Learning objectives
- 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 share access to some piece of data
- Rust’s
Sync
and Send
traits
Concurrent vs. Parallel
- Concurrent programming -> different parts of a program execute independently
- Parallel programming -> different parts of a program execute at the same time
- “Fearless Concurrency” applies to both concurrent and parallel programming
Fearless Concurrency with Rust
- Rust’s ownership and type system manages memory safety AND concurrency problems
- This helps convert run-time errors into compile-time errors
- You can fix your code while you are working on it, rather than after it has been shipped
Opearting systems run programs concurrently
- Operating systems run executed code in a process
- Multiple processes can be run at once
Programs can run their parts concurrently as well
- Within a program, independent parts can also run simultaneously
- The features that run these independent parts are called threads
- e.g. Web Servers have multiple threads to respond to multiple requests at once
Typical challenges of concurrency
- Race conditions: threads accessing resources in an unexpected order
- Deadlocks: where two threads are waiting for each other, preventing progress
- Bugs that only happen in certain contexts and are difficult to reproduce
Thread models
- Rust’s standard library uses a 1:1 model of thread implementation
- A program uses one operating system thread per one language thread
- Different crates offer different implementations, with different trade-offs
Spawning a thread in Rust
- To create a new thread, we use
thread::spawn
, and pass it to a closure
- The closure should contain the code that we want to run in that thread
- When the main thread of a Rust program completes, all spawned threads are shut down
- Code example!
Join handles
- Calling
join
on the handle blocks the currently running thread
- It unblocks when the thread represented by the handle terminates
- Code examples!
Transfer ownership with move
- Using the
move
keyword with closures passed to thread::spawn
- This transfer ownership of the values it uses from the environment to the thread
- Code example!
Message Passing Concurrency
- Threads communicate to each other by passing messages with data
- “Do not communicate by sharing memory, share memory by communicating”
Channels
- In Rust, message passing concurrency is achieved using
channels
- Channels can be thought of as the directional channel of a river
- Messages placed in the channel travel downstream until the end
Transmitters and Receivers
- Upstream: code calls methods on the
transmitter
with the data you want to send
- Downstream: code checks the
receiving
end for arriving messages
- A transmitter and receiver can be initialized with
mpsc::channel()
- Code example!
Shared-State Concurrency
- Instead of passing messages, multiple threads could also access the same shared data
- Channels are similar to single ownership
- Shared memory is similar to shared ownership
- Mutexes are a common primitive used for sharing memory between owners
Mutexes
- Short for Mutual exclusion
- A mutex allows only one owner to access data at any given time
- To access data in a mutex, a thread must ask to acquire the mutex’s lock
- The lock keeps track of who currently has exclusive access to the data
- “Mutexes guard the data they hold with a locking system”
Two rules of mutexes
- Attempt to acquire their lock before using their data
- When you’re done with the data, you must unlock the data
- Code example!
Sharing a Mutex between threads
Mutex<T>
is not safe to share between multiple threads by default
- Use
Arc
(Atomic Reference Counting) to enable multiple ownership.
- Code example!
Extending Concurrency with Sync
and Send
- Concurrency can be extended in Rust using the
Sync
and Send
traits
Send
indicates that ownership of values of that type can move between threads
Sync
indicates that it is safe for that type to be referenced from multiple threads