Ok, so I want to make a NewType for Arc<Mutex<MyType>>
because I want to encapsulate the locking in a public API. ie, locking should occur internally and users don't need to lock().unwrap()
themselves all over the place, and I want to prevent that anyway.
My problem is that I can't seem to share my NewType across threads and mutate it. Maybe I am missing a trick, eg impl some missing Trait(s), or maybe it is just not possible...?
I discovered this issue when I tried to write a little program to test the locking in my NewType. The program runs a loop where each iteration reads all entries and also writes to all entries. The idea is to ensure that reads see a consistent (snapshot) view of the data. Here's a simplified version of that program, just wrapping a Vec<usize>
.
use std::sync::{Arc, Mutex};
use std::thread;
struct MyVec{ inner: Arc<Mutex<Vec<usize>>>, }
impl MyVec {
fn new() -> Self {
Self{ inner: Arc::new(Mutex::new(vec![])) }
}
fn replace(&mut self, vals: &[usize]) {
let mut lock = self.inner.lock().unwrap();
lock.clear();
for v in vals.iter() {
lock.push(*v);
}
}
fn len(&self) -> usize {
self.inner.lock().unwrap().len()
}
fn push(&self, v: usize) {
self.inner.lock().unwrap().push(v);
}
fn set(&self, k: usize, v: usize) {
self.inner.lock().unwrap()[k] = v;
}
fn get_all(&self) -> Vec<usize> {
self.inner.lock().unwrap().clone()
}
}
fn main() {
let mut vec = MyVec::new();
for i in 0..500 {
vec.push(i);
}
let orig = vec.get_all();
thread::scope(|s| {
for _i in 0..100 {
let gets = s.spawn(|| {
assert_eq!(orig, vec.get_all());
});
let sets = s.spawn(|| {
for j in 0..vec.len() {
vec.set(j, 50);
}
});
let _ = gets.join();
let _ = sets.join();
vec.replace(&orig);
}
});
}
This won't compile.
error[E0502]: cannot borrow `vec` as mutable because it is also borrowed as immutable
However, if I replace main() with a version that wraps MyVec
with an outer Arc<Mutex<..>>
, then it works.
fn main() {
let vec = MyVec::new();
for i in 0..500 {
vec.push(i);
}
let orig = vec.get_all();
let vec = Arc::new(Mutex::new(vec));
thread::scope(|s| {
for _i in 0..100 {
let gets = s.spawn(|| {
assert_eq!(orig, vec.lock().unwrap().get_all());
});
let sets = s.spawn(|| {
let lock = vec.lock().unwrap();
for j in 0..lock.len() {
lock.set(j, 50);
}
});
let _ = gets.join();
let _ = sets.join();
vec.lock().unwrap().replace(&orig);
}
});
}
But that defeats the point of the NewType and I'm back where I started from.
So... is there some way to make the first version compile and run cleanly?
replace()
needs to take &self
, it will still work since Mutex
employs interior mutability.
However, note that your assert!()
will fail, since you're racing the set()
and the get_all()
.
Some other notes: instead of the loop in replace()
you can use extend_from_slice()
, and the Arc
is redundant - you can use just Mutex
.