Rust threading: Difference between revisions
From wikinotes
(Created page with "= Documentation = <blockquote> {| class="wikitable" |- | rust book: concurrency || https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html |- |} </blockquote><!-- Documentation --> = Basics = <blockquote> Since rust already manages ownership semantics, you don't really need to deal with thread affinity.<br> Simply pass a closure to a thread, move any params to it, and call it a day. thead without outer scope access <syntaxhighlight lang="rust"> use std::thread;...") |
(→Traits) |
||
(11 intermediate revisions by the same user not shown) | |||
Line 4: | Line 4: | ||
|- | |- | ||
| rust book: concurrency || https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html | | rust book: concurrency || https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html | ||
|- | |||
| <code>std::sync</code> || builtin synchronization primitives | |||
|- | |- | ||
|} | |} | ||
Line 19: | Line 21: | ||
fn main() { | fn main() { | ||
let handle = thread::spawn(|| { | let handle = thread::spawn(|| { // <<-- note closure without 'move' | ||
for i in 1..=5 { | for i in 1..=5 { | ||
println!("step {}/5..", i); | println!("step {}/5..", i); | ||
Line 36: | Line 38: | ||
fn main() { | fn main() { | ||
let name = String::from("alex"); | let name = String::from("alex"); | ||
let handle = thread::spawn(move || { | let handle = thread::spawn(move || { // <-- 'move' changes thread affinity of closure vars | ||
for _ in 1..=5 { | for _ in 1..=5 { | ||
println!("hi {}!", name); | println!("hi {}!", name); | ||
Line 47: | Line 49: | ||
</blockquote><!-- Basics --> | </blockquote><!-- Basics --> | ||
== | == Synchronization == | ||
<blockquote> | <blockquote> | ||
rust | == mspc::channel == | ||
<blockquote> | |||
multiple producer, single consumer, FIFO queue.<br> | |||
do work in threads, have eventloop in main thread. | |||
<syntaxhighlight lang="rust"> | |||
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"); | |||
} | |||
</syntaxhighlight> | |||
</blockquote><!-- mspc::channel --> | |||
== mutexes == | |||
<blockquote> | |||
rust mutexes are a bit unique, in that they wrap the object you are protecting.<br> | |||
locking the mutex provides access to the object,<br> | |||
and when it falls out of scope, the lock is released. | |||
<syntaxhighlight lang="rust"> | |||
let m = Mutex::new(5); | |||
{ | |||
let mut num = m.lock().unwrap(); | |||
*num = 6; // mutate the protected value | |||
} // lock is released | |||
</syntaxhighlight> | |||
If a mutex is shared between threads, wrap it in <code>Arc<T></code> so the reference is not deleted.<br> | |||
This behaves like a <code>Rc<T></code> for threads (see [[rust pointers]] for more details). | |||
<syntaxhighlight lang="rust"> | <syntaxhighlight lang="rust"> | ||
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()); | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</blockquote><!-- | </blockquote><!-- mutexes --> | ||
</blockquote><!-- Synchronization --> | |||
= Ownership = | |||
<blockquote> | |||
== Traits == | |||
<blockquote> | |||
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]]) | |||
</blockquote><!-- Traits --> | |||
</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)