An Introduction to Rust -- Part 2

 Rust
 

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 and Send 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::thread;
use std::time::Duration;

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));
}
}

(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::thread;
use std::time::Duration;

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));
}
});

for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}

handle.join().unwrap();
}

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
2
3
4
5
6
7
8
9
10
11
use std::thread;

fn main() {
let v = vec![1, 2, 3];

let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});

handle.join().unwrap();
}

Using Message Passing to Transfer Data Between Threads

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use std::thread;
use std::sync::mpsc;

fn main() {
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);
}

(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::thread;
use std::sync::mpsc;

fn main() {
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
println!("val is {}", val); // wrong
});

let received = rx.recv().unwrap();
println!("Got: {}", received);
}

(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}

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
39
use 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
2
3
4
5
6
7
8
9
10
11
use std::sync::Mutex;

fn main() {
let m = Mutex::new(5);

{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}

(Mutex<T> is a smart pointer. The call to lock return a smart pointer called
MutexGuard, 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
21
use 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 before moving ownership to
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
23
use 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 (atomically reference counted) is a type like Rc that is safe to
use in concurrent situations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
}

println!("Result: {}", *counter.lock().unwrap());
}

Similarities Between RefCell<T>/Rc<T> and Mutex<T>/Arc<T>

We can use Mutex, which provides interior mutability, to mutate contents
inside an Arc just like RefCell to Rc.

Extensible Concurrency with the Sync and Send Traits

Two concurrency concepts are embedded in the language: the std::marker traits
Sync 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.

Implementing Send and Sync Manually is Unsafe


References

The Rust Programming Language