Rust threading: Difference between revisions
From wikinotes
No edit summary |
(→Traits) |
||
Line 120: | Line 120: | ||
== Traits == | == Traits == | ||
<blockquote> | <blockquote> | ||
* <code>Send</code> | Automatically Applied: | ||
* <code>Send</code> implementors can have their ownership transferred between threads | |||
* <code>Sync</code> implementors indicate they can safely be referenced by multiple threads | |||
Manually Applied | |||
* <code>Arc<T></code> trait implementors allow reference-counting between threads (see [[rust pointers]]) | * <code>Arc<T></code> trait implementors allow reference-counting between threads (see [[rust pointers]]) | ||
</blockquote><!-- Traits --> | </blockquote><!-- Traits --> | ||
</blockquote><!-- Ownership --> | </blockquote><!-- Ownership --> |
Latest revision as of 04:19, 11 February 2023
Documentation
rust book: concurrency https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html std::sync
builtin synchronization primitives
Basics
Since rust already manages ownership semantics, you don't really need to deal with thread affinity.
Simply pass a closure to a thread, move any params to it, and call it a day.thead without outer scope access
use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { // <<-- note closure without 'move' for i in 1..=5 { println!("step {}/5..", i); thread::sleep(Duration::from_secs(1)); } }); handle.join().expect("unable to join thread"); }thread that moves outer-scope into thread's closure
use std::thread; use std::time::Duration; fn main() { let name = String::from("alex"); let handle = thread::spawn(move || { // <-- 'move' changes thread affinity of closure vars for _ in 1..=5 { println!("hi {}!", name); thread::sleep(Duration::from_secs(1)); } }); handle.join().expect("unable to join thread"); }
Synchronization
mspc::channel
multiple producer, single consumer, FIFO queue.
do work in threads, have eventloop in main thread.use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); let handle = thread::spawn(move|| { tx.send(123).unwrap(); }); println!("hello to {}", rx.recv().unwrap()); handle.join().expect("unable to join thread"); }mutexes
rust mutexes are a bit unique, in that they wrap the object you are protecting.
locking the mutex provides access to the object,
and when it falls out of scope, the lock is released.let m = Mutex::new(5); { let mut num = m.lock().unwrap(); *num = 6; // mutate the protected value } // lock is releasedIf a mutex is shared between threads, wrap it in
Arc<T>
so the reference is not deleted.
This behaves like aRc<T>
for threads (see rust pointers for more details).use std::thread; use std::sync::Arc; use std::sync::Mutex; fn main() { let counter_lock_arc = Arc::new(Mutex::new(0)); let counter_lock = Arc::clone(&counter_lock_arc); // clone before we move ownership to closure let spawn_thread = || { let counter_lock = Arc::clone(&counter_lock_arc); thread::spawn(move|| { let mut count = counter_lock.lock().unwrap(); *count += 1; }) }; (1..=3) .map(|_| spawn_thread()) .for_each(|h| h.join().expect("unable to join thread")); println!("count: {}", counter_lock.lock().unwrap()); }
Ownership
Traits
Automatically Applied:
Send
implementors can have their ownership transferred between threadsSync
implementors indicate they can safely be referenced by multiple threadsManually Applied
Arc<T>
trait implementors allow reference-counting between threads (see rust pointers)