π¦ Rust Master Class - Chapter 14: Concurrency
Six conversations at a dinner party. I tried to listen to all of them. Couldn't β I'm single-threaded. So I learned to switch. That's concurrency: not doing more, but doing better.
Concurrency in Rust is designed to be safe and efficient, leveraging the ownership system to prevent data races at compile time. The sources outline several key mechanisms for handling concurrent execution: threads, message passing (channels), shared state (mutexes), and asynchronous programming (async-await).
1. Threads
Threads allow a program to perform multiple tasks simultaneously. Rust uses the std::thread module for basic thread management.
- Spawning Threads: You use
thread::spawnto create a new thread, passing it a closure containing the code to execute . - Thread Control:
-
thread::sleep: Pauses the current thread for a specified duration . -
thread::yield_now: Voluntarily gives up the thread's current time slice to the OS scheduler, allowing other threads to run .
-
- Ownership: When passing data into a thread, you often use the
movekeyword with the closure to transfer ownership of variables from the parent environment to the spawned thread .
Code Example:
use std::thread;
use std::time::Duration;
fn main() {
// Create a new variable
let handle = thread::spawn(|| {
// Output to console
println!("Thread started!");
thread::yield_now(); // Give other threads a chance
});
handle.join().unwrap(); // Wait for the thread to finish
}
2. Communication through Channels
Rust implements "message passing" using mpsc (Multiple Producer, Single Consumer) channels. This follows the philosophy: "Do not communicate by sharing memory; instead, share memory by communicating" .
- Creation:
mpsc::channel()returns a tuple containing a Sender (tx) and a Receiver (rx) . - Multiple Producers: You can clone the sender (
tx.clone()) to allow multiple threads to send messages to the same single receiver . - Sending/Receiving: Use
tx.send(val)to pass data andrx.recv()to block the current thread until a message is received .
Code Example:
use std::sync::mpsc;
use std::thread;
fn main() {
// Create a new variable
let (tx, rx) = mpsc::channel();
// Create a new variable
let tx1 = tx.clone();
thread::spawn(move || {
tx1.send(10).unwrap();
});
// Output to console
println!("Received: {}", rx.recv().unwrap());
}
3. Sharing State with Mutex and Arc
When threads must access the same data, Rust uses Shared State concurrency.
-
Mutex<T>(Mutual Exclusion): Ensures only one thread can access data at a time by requiring a thread to "lock" the mutex before use . -
Arc<T>(Atomic Reference Counting): To share aMutexacross multiple threads, it must be wrapped in anArc.Arcis a thread-safe version ofRcthat allows multiple ownership across threads [7-9]. - Synchronization Barriers:
std::sync::Barriercan be used to make multiple threads wait until they all reach a certain point in execution .
Code Example:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Create a new variable
let counter = Arc::new(Mutex::new(0));
// Create a mutable variable
let mut handles = vec![];
for _ in 0..10 {
// Create a new variable
let counter = Arc::clone(&counter);
// Create a new variable
let handle = thread::spawn(move || {
// Create a mutable variable
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
// Join threads...
}
4. Async-Await
Async-await is used for non-blocking concurrency, particularly useful for I/O-bound tasks. It allows a single thread to handle many tasks by "yielding" when waiting for a resource .
- Lazy Execution: Async functions are lazy; calling an
async fndoes not execute it immediately but returns aFuturethat must be polled or awaited to run . - The Runtime: Rustβs standard library does not include an async runtime. Sources frequently reference Tokio as a common external runtime for executing async tasks .
-
tokio::spawn: Used to run tasks concurrently within the async runtime . -
tokio::select!: Allows waiting on multiple asynchronous computations at once, returning as soon as the first one completes .
Code Example:
#[tokio::main]
async fn main() {
// Create a new variable
let task = tokio::spawn(async {
// Output to console
println!("Async task running");
});
task.await.unwrap(); // Polling the future to completion
}
π Download the full PDF: https://drive.google.com/file/d/1qMZS9TdB7wqPMBJfpUw614X5sKOruLQu/view?usp=sharing
Part 14 of the Rust Master Class series β STEM EdTech | Automation Consulting | Rust Tutoring
RustLang #Programming #LearnToCode #STEM #EdTech
π Practice Resources
GitHub Repository: https://github.com/PacktPublishing/Rust-Programming-Master-Class-from-Beginner-to-Expert
Try it yourself: https://play.rust-lang.org/
Run the code from this chapter in the Rust playground, then clone the repo to continue your Rust journey!
Part 14 of the Rust Master Class series β STEM EdTech | Automation Consulting | Rust Tutoring
Top comments (0)