5 ms·
> e.g. copy "commands" or whatever in and out of queues vs. just taking a lock around some shared data struct Shared { /* ... */ } #[derive(Clone)
by duped 4y ago
> e.g. copy "commands" or whatever in and out of queues vs. just taking a lock around some shared data
struct Shared { /* ... */ }
#[derive(Clone)
struct Context {
shared:Arc<Mutex<Shared>>
}
impl Context {
fn do_stuff (&self) {
let shared = self.shared.lock();
/* ... */
}
fn do_more_stuff (&self) {
let shared = self.shared.lock();
}
}
What's wrong with that? You have a bunch of safety guarantees here :
- mutex poisonings are detected (you can use try_lock to recover from them)
- the shared data isn't leaked and guaranteed to live long enough in each calling context
- the Mutex implementation doesn't suck and iirc supports reentrant code without deadlocks, making it safe to use on the same thread
- it's really easy to call `context.clone()` to send it wherever you want
- masklinn 4y ago> the Mutex implementation doesn't suck and iirc supports reentrant code without deadlocks I don’t think that’s true, at least in the “rust mutexes are reentrant” sense: a mutex hands out &mut refs, a reentrant mutex would mean concurrent &mut, which is illegal. However rust does allow handing out the one &mut to callees, without fear that it will be leaked and outlive the lock or anything, so this sort of “reentrancy” is fine if you split functions into lock acquisition and actual processing. IME deadlocks (and self-deadlocks) remain the biggest issue in concurrent rust. Lock ordering concerns are especially frustrating when fine-locking.
- duped 4y agoIf you do something like this #[derive(Clone)] struct S { a: Arc<Mutex<A>> } impl S { fn reentrant(&self) { let cloned = self.cloned(); let locked = self.a.lock(); if condition { cloned.reentrant(); } else { /* do stuff */ } } I believe it will not deadlock. Reentrant mutexes are pretty common implementations - is that not true in Rust right now?
- anderskaseorg 4y agoYour code does deadlock. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e26f0fc589740eaf12da87c00004fe64 https://play.rust-lang.org/?version=stable&mode=debug&editio... The parent comment correctly explained that it would be incorrect for a Mutex<A> to hand out unique references &mut A to multiple call frames simultaneously. If it did, one of them could be passed to another thread and accessed concurrently with the other one to create race conditions. https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.lock https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.... “The exact behavior on locking a mutex in the thread which already holds the lock is left unspecified. However, this function will not return on the second call (it might panic or deadlock, for example).” The parking_lot crate has a ReentrantMutex<A> that would not deadlock here. It hands out shared references &A instead of unique references &mut A. If you want to be able to mutate the value, you can wrap it in a type with interior mutability (Cell or RefCell), and then the type system will prevent you from passing those references to other threads. https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html https://docs.rs/parking_lot/latest/parking_lot/type.Reentran...
- masklinn 4y ago> The parent comment correctly explained that it would be incorrect for a Mutex<A> to hand out unique references &mut A to multiple call frames simultaneously. If it did, one of them could be passed to another thread and accessed concurrently with the other one to create race conditions. FWIW they wouldn't even need to be moved between threads. IIRC creating two independent &mut to the same object is one of the instant UBs, it already is an invalid program state.
- School-Cotton 4y ago> (you can use try_lock to recover from them) Slightly inaccurate. You can recover from them with `lock` too. In fact, to _not_ attempt to recover from them, your code should have looked like: let shared = self.shared.lock().expect("mutex poisoned!") as `lock` returns a `Result`. The difference between `try_lock` and `lock` is unrelated: `try_lock` fails fast if it's not possible to take the mutex immediately.