An Introduction to the Rust language – part 2.
(Note: All contents in this post comes from the Rust Programming Language)
13. Functional Language Features: Iterators and Closures
16. Fearless Concurrency
- How to create threads to run multiple pieces of code at the same time.
- Message-passing concurrency, where channels send messages between threads.
- Shared-state concurrency, where multiple threads have access to some piece of data.
- The
Sync
andSend
traits, which extend Rust’s concurrency guarantees to
user-defined types as well as types provided by the standard library.
Using Threads to Run Code Simultaneously
Create a New Thread with spawn
1 | use std::thread; |
(Note: the new thread will be stopped when the main thread ends, whether or not
it has finished running.)
Waiting for All Threads to Finish Using join
Handles
The return type of thread::spawn
is JoinHandle
. A JoinHandle is an owned
value that, when we call the join
method on it, will wait for its thread to
finish.
1 | use std::thread; |
Using move
closures with Threads
The move
closure is often used alongside thread::spawn
because it allows you
to use data from one thread in another thread.
1 | use std::thread; |
Using Message Passing to Transfer Data Between Threads
1 | use std::thread; |
(recv method will block the main thread’s execution and wait until a value is sent
down the channel. Once a value is sent, recv will return it in a Result<T, E>.
When the sending end of the channel closes, recv will return an error to signal
that no more values will be coming.
The try_recv method doesn’t block, but will instead return a Result<T, E>
immediately: an Ok value holding a message if one is available and an Err
value if there aren’t any messages this time.)
Channels and Ownership Transference
1 | use std::thread; |
(The send function takes ownership of its parameter, and when the value is moved,
the receiver takes ownership of it.)
Sending Multiple Values and Seeing the Receiver Waiting
1 | use std::sync::mpsc; |
Create Multiple Producers by Cloning the Transmitter
mpsc is an acronym for multiple producer, single consumer.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx); // clone the transmitter
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);
}
}
Shared-State Concurrency
Using Mutexes to Allow Access to Data from One Thread at a Time
A Mutex (mutual exclusion) allows only one thread to access some data at any
given time. To access the data in a mutex, a thread must first signal that it
wants access by asking to acquire the mutex’s lock.
The API of Mutex<T>
1 | use std::sync::Mutex; |
(Mutex<T>
is a smart pointer. The call to lock return a smart pointer calledMutexGuard
, wrapped in a LockResult that we handled with the call to unwrap.
The MutexGuard smart pointer implements Deref
to point at our inner data; the
smart pointer also have a Drop
implementation that releases the lock automatically
when a MutexGuard goes out of scope.)
Sharing a Mutex<T>
Between Multiple Threads
(wrong, see below)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
Multiple Ownership with Multiple Threads
Wrap the Mutex<T>
in Rc<T>
and clone the Rc
the thread.
(wrong, see below)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23use std::rc::Rc;
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Rc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Rc::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();
}
println!("Result: {}", *counter.lock().unwrap());
}
Atomic Reference Counting with Arc<T>
Arc
use in concurrent situations.
1 | use std::sync::{Arc, Mutex}; |
Similarities Between RefCell<T>
/Rc<T>
and Mutex<T>
/Arc<T>
We can use Mutex
inside an Arc
Extensible Concurrency with the Sync
and Send
Traits
Two concurrency concepts are embedded in the language: the std::marker
traitsSync
and Send
.
Allowing Transference of Ownership Between Threads with Send
The Send
marker trait indicates that ownership of the type implementing Send
can be transferred bwtween threads. Almost every Rust type is Send, but there
are some exceptions, including Rc<T>
.
Any type composed entirely of Send types is automatically marked as Send as well.
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. In other words, any type T is Sync if
&T is Send. Similar to Send, primitive types are Sync, and types composed
entirely of types are Sync are also Sync.
The RefCell<T>
type and the family of related Cell<T>
types are not Sync
.
The Rc<T>
is not Sync, but the Mutex<T>
is Sync.