Multithreading Patterns
Multithreading in Rust introduces some unique challenges. Unlike many other lanaguages, the compiler is strict on enforcing safe access to shared values.
Move
Idea: Move ownership of a value exclusively to a new thread.
#![allow(unused)] fn main() { use std::thread; let values = vec![1, 2, 3, 4, 5]; let t = thread::spawn(move || { // Thread takes exclusive ownership of `values` for value in values { println!("{}", value); } }); t.join(); }
Threadsafe
Idea: Make a type threadsafe by protecting the internal state with a Mutex
.
This type implements Clone
, so it can be easily shared between threads.
#![allow(unused)] fn main() { use std::sync::{Arc, Mutex}; #[derive(Clone)] struct Counter(Arc<Mutex<u64>>); impl Counter { fn new() -> Self { // Internal state is protected by a Mutex Counter(Arc::new(Mutex::new(0))) } fn value(&self) -> u64 { *self.0.lock().unwrap() } fn increment(&self) { *self.0.lock().unwrap() += 1; } } }
Handle
Idea: Split a class into an exclusive owned type and a thread-safe handle.
#![allow(unused)] fn main() { use std::io; use std::sync::mpsc; /// Worker with exclusive access to a resource. struct Worker { channel: mpsc::Receiver<Message> } impl Worker { /// Returns ([`Worker`], [`Handle`]) pair. fn new() -> (Self, Handle) { // Channel for sending messages to the worker. let (tx, rx) = mpsc::channel(); (Worker { channel: rx }, Handle { channel: tx }) } /// Function which takes exclusive access of [`Worker`]. fn run(&mut self) { loop { while let Ok(message) = self.channel.recv() { match message { Message::Foo(value) => println!("Foo: {}", value), } } } } } /// Handle for interfacing with the worker. #[derive(Clone)] struct Handle { channel: mpsc::Sender<Message> } impl Handle { fn foo(&self, value: i32) { self.channel.send(Message::Foo(value)).unwrap() } } /// Messages that can be sent to [`Worker`]. enum Message { Foo(i32) } }