Rust has a 1:1 thread system — every thread is a physical thread managed by
the OS. Thread is spawned using thread::spawn
:
use std::thread;
fn main() {
for i in 1..10 {
// may or may not finish
thread::spawn(move || {
println!("i = {}", i);
});
}
}
To wait for a thread from the parent thread, the spawned thread's handle can
be joined using the join
method:
use std::thread;
fn main() {
(1..10)
.map(|i| thread::spawn(move || println!("i = {}", i)))
.for_each(|h| h.join().unwrap()) // all will finish
}
The only two language features for concurrency — all others are just an abstraction above these.
The Send
trait indicates that the type's ownership can be transferred between
threads. Almost all types implement this automatically, except Rc<T>
, raw
pointers, and few others.
The Sync
trait indicates that it is safe for the type implementing Sync
to
be referenced from multiple threads. Most types are Sync
, except runtime
borrow types like RefCell<T>
.
Any type
T
isSync
if&T
isSend
.
All compound types that are composed of types that implement Send
also
implicitly implement Send
. Same goes for Sync
.
Thread safe version of Rc<T>
, the Arc<T>
, or atomic reference counter,
implements the Clone
trait that creates a reference pointer to a value on the
heap:
use std::sync::Arc;
use std::thread;
fn main() {
let swag = Arc::new("swag");
for _ in 0..10 {
let swag = swag.clone();
thread::spawn(move || {
println!("{:?}", swag); // swag is moved
});
}
}
It behaves just like Rc<T>
, but has a slight performance cost due to the
thread safety.
Channels send data from multiple threads into a single consuming thread to process the results:
use std::sync::mpsc; // multiple producers single consumer
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("swag").unwrap();
});
let r = rx.recv().unwrap();
println!("yolo {}", r);
}
The rx
receiver can also be used as an iterator.
The transmitter tx
needs to be cloned to be used in multiple threads:
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
(1..10)
.map(|x| {
let tx = tx.clone();
thread::spawn(move || {
tx.send(x).unwrap();
})
})
.for_each(|h| h.join().unwrap());
drop(tx); // rx waits until all clones of tx are dropped
for r in rx {
println!("{}", r);
}
}
Short for mutual exclusion, it is a mechanism for ensuring only a single thread accesses a mutable memory at one time.
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
} // MutexGuard goes out of scope, mutex is released
println!("m = {:?}", m);
}
The type T
in a Mutex<T>
must be Send
.
Sharing a Mutex<T>
instance between multiple threads is achieved using
Arc<T>
that handles reference counting:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
(0..10)
.map(|_| {
let counter = Arc::clone(&counter);
thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
})
})
.for_each(|h| h.join().unwrap());
println!("Result: {}", *counter.lock().unwrap());
}
Basically Mutex<T>
, except it allows multiple readers and one writer:
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
fn main() {
let counter = Arc::new(RwLock::new(0));
let pc = counter.clone();
let producer = thread::spawn(move || loop {
if let Ok(mut w) = pc.write() {
*w += 1;
thread::sleep(Duration::from_millis(500));
}
});
let rc_1 = counter.clone();
let consumer_1 = thread::spawn(move || loop {
if let Ok(v) = rc_1.read() {
// One reads here
println!("counter = {}", v);
thread::sleep(Duration::from_millis(250));
}
});
let rc_2 = counter.clone();
let consumer_2 = thread::spawn(move || loop {
if let Ok(v) = rc_2.read() {
// Another one reads here, Mutex would deadlock
println!("counter^2 = {}", *v * *v);
thread::sleep(Duration::from_millis(250));
}
});
producer.join().unwrap();
consumer_1.join().unwrap();
consumer_2.join().unwrap();
}
If this was done with a Mutex<T>
, the readers would get deadlocked. The
drawback is that in RwSync<T>
, the T
must be both Send
and Sync
.