17 ms·
Not since I’ve use it either. I may be missing something since I’ve only used async Rust, in what way does Rust say “you need a lock here”? If it does that then
by dcow 4y ago
Not since I’ve use it either. I may be missing something since I’ve only used async Rust, in what way does Rust say “you need a lock here”? If it does that then I stand corrected and I may just have to drop async Rust altogether and checkout crossbeam + rayon that everyone raves about.
- steveklabnik 4y agoRust has two traits, Send and Sync. Send means "this can be transferred to another thread," and Sync means "this can be accessed via a reference in another thread. Here's some (contrived!) example code (for one thing I'm using thread::scope because I don't want to deal with joining the threads): use std::thread; use std::rc::Rc; fn main() { let v = Rc::new(vec![1, 2, 3]); thread::scope(|s| { s.spawn(|| { do_work(v.clone()); }); s.spawn(|| { do_work(v.clone()); }); }); } fn do_work(v: Rc<Vec<i32>>) { unimplemented!() } This gives: error[E0277]: `Rc<Vec<i32>>` cannot be shared between threads safely --> src/main.rs:8:17 | 8 | s.spawn(|| { | ___________-----_^ | | | | | required by a bound introduced by this call 9 | | do_work(v.clone()); 10 | | }); | |_________^ `Rc<Vec<i32>>` cannot be shared between threads safely | Rc is not thread-safe. We try to send it into some threads. It doesn't work. Switching to Arc, which does use atomic reference counts and therefore is thread-safe, does. Same principle would apply with Mutex if we were trying to modify the vector, Rust will yell at us. One really really nice thing about this is that it'll check no matter how for "down" into the details the thread unsafety is. There's a story Niko told in a presentation of his how he was doing some refactoring and added a type that wasn't thread-safe like, four or five layers down from where the threading happened. rustc caught it immediately, and therefore, it was obvious. Would have been a heisenbug in other languages. Async Rust also uses Send/Sync, for example, tokio::spawn requres a Send bound, just like spawning a thread does. I do know there are some tricky deadlock cases there, if I recall? But deadlocking isn't what I'm talking about, no aspect of Rust statically prevents those.
- dcow 4y agoI understand send and sync. I see what you’re saying. Though note, even if you pass around Arcs the inner value still has to be Mutex or RwLock. But I do see how Rust makes this more structured. Honestly with async it’s usually enough to just make sure your types are Send and Sync and clone them so that’s really the extent of what I normally have to deal with. Re deadlocking: With async runtimes, since you have a fixed threadpool, if you use the normal locks from the stdlib you can deadlock or more accurately stall your program because all available executor threads are blocked waiting on a lock. If the executor is starved the task that would unlock the stalled threads never gets scheduled. It’s a problem unique to the task executor paradigm (the thread per task version of the program would be logically correct and never deadlock or a version that used yielding locks). Not sure if thats exactly what you’re talking about, but, it’s a part of the language/experience I think could use some work. Would be nice if the structure that exists around data races could also exist around blocking vs yielding calls from async tasks.