rust fearless concurrency
Using threads to run code simultaneously
use std::thread;
use std::time::Duration;
// hi number 1 from the main thread!
// hi number 1 from the spawned thread!
// hi number 2 from the main thread!
// hi number 2 from the spawned thread!
// hi number 3 from the main thread!
// hi number 3 from the spawned thread!
// hi number 4 from the main thread!
// hi number 4 from the spawned thread!
// hi number 5 from the spawned thread!
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
use std::thread;
use std::time::Duration;
// waiting for all threads to finish using join handles
// hi number 1 from the spawned thread!
// hi number 2 from the spawned thread!
// hi number 3 from the spawned thread!
// hi number 4 from the spawned thread!
// hi number 5 from the spawned thread!
// hi number 6 from the spawned thread!
// hi number 7 from the spawned thread!
// hi number 8 from the spawned thread!
// hi number 9 from the spawned thread!
// hi number 1 from the main thread!
// hi number 2 from the main thread!
// hi number 3 from the main thread!
// hi number 4 from the main thread!
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
handle.join().unwrap();
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
use std::thread;
fn main() {
let v = vec![1, 2, 3];
// use move keyword to force the closure to take ownership of
// the values
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
Using message passing to transfer data between threads
use std::sync::mpsc;
use std::thread;
fn main() {
// mspsc::channel returns a tuple:
// tx (transmitter) is the sending end
// rx (receiver) second is the receiving end
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
The receiving end of a channel has two useful methods:
recv
: block the main thread’s execution and wait until a value is sent down the channeltry_recv
: doesn’t block but will instead return aResult<T, E>
immediately: anOk
value holding a message if one is available and anErr
value if there aren’t any messages this time- useful if the thread has other work to do while waiting
Channels and ownership transference
The send
function takes ownership of its parameter, and when the
value is moved, the receiver takes ownership of it. This stops us
from accidentally using the value again after sending it.
Sending multiple values and seeing the receiver waiting
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
}
Producing multiple producers by cloning the transmitter
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// --snip--
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
// --snip--
}
The output should look something like this:
Got: hi
Got: more
Got: from
Got: messages
Got: for
Got: the
Got: thread
Got: you
Shared-state concurrency
Using mutexes to allow access to data from one thread at a time
Mutex allows only one thread to access some data at any given time.
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
// prints: m = Mutex { data: 6, poisoned: false, .. }
println!("m = {:?}", m);
}
Mutex<T>
is smart pointer.
Multiple ownership with multiple threads
Rc<T>
is not safe to share across threads.
Atomic reference counting with Arc<T>
Arc<T>
is a type like Rc<T>
that is safe to use in concurrent situations.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// prints: Result: 10
println!("Result: {}", *counter.lock().unwrap());
}
Extensible concurrency with the Sync
and Send
traits
Allowing transference of ownership between threads with
Send
The Send
marker trait indicates that ownership of values of the
type implementing Send
can be transferred between threads.
Allowing access from multiple threads with
Sync
The Sync
marker trait indicates that it is safe for the type
implementing Sync
to be referenced from multiple threads.
Manually implementing these traits involves implementing unsafe Rust code.