Concurrency and Multithreading in Rust: Advanced Techniques

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Concurrency and Multithreading in Rust: From Basics to Advanced Techniques



Rust has earned a reputation as one of the safest systems programming languages, largely due to its ownership model and type system, which prevents data races at compile time. However, concurrency is not only about safety but also about efficiency. In this article, we will explore advanced aspects of working with concurrency and multithreading in Rust: from basic synchronization primitives to modern asynchronous runtimes. The material is intended for developers already familiar with the language basics.



1. Safe Multithreading: Send, Sync, and Arc<Mutex<T>>



The foundation of safe multithreading in Rust is two key traits: Send and Sync. A type that implements Send can be safely transferred between threads. A type that implements Sync can be safely shared between threads by reference. Most standard types (e.g., i32, String) automatically implement both traits if they contain only Send/Sync data.



For shared access to mutable data from multiple threads, the Arc<Mutex<T>> pattern is often used. Arc (atomic reference counting) allows sharing ownership between threads, while Mutex provides mutual exclusion.



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


It is important to remember: Mutex in Rust is not just a lock, but a container that protects data. Acquiring a lock returns a smart pointer MutexGuard, which automatically releases the lock when it goes out of scope. This eliminates the classic "forgot to unlock" error.



Advanced Tip: RwLock instead of Mutex



If your data is frequently read but rarely modified, use RwLock. It allows multiple threads to read data simultaneously but blocks writes while there is at least one reader. This can significantly improve performance in "readers more than writers" scenarios.



use std::sync::RwLock;

let data = RwLock::new(vec![1, 2, 3]);

// Reading — multiple threads can read simultaneouslylet read_guard = data.read().unwrap();println!("First element: {}", read_guard[0]);drop(read_guard); // Explicitly release the lock

// Writing — exclusive accesslet mut write_guard = data.write().unwrap();write_guard.push(4);


2. Channels: Message Passing Between Threads



Channels are an alternative to shared memory. Rust has two main types of channels: std::sync::mpsc (Multi-Producer, Single-Consumer) and crossbeam-channel (Multi-Producer, Multi-Consumer).



The standard mpsc is well-suited for "one consumer, many producers" scenarios. It works on a queue principle: senders place messages, the receiver retrieves them in FIFO order.



use std::sync::mpsc;use std::thread;

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

let tx1 = tx.clone(); thread::spawn(move || { tx1.send("Message from thread 1").unwrap(); });

thread::spawn(move || { tx.send("Message from thread 2").unwrap(); });

for received in rx { println!("Received: {}", received); }}


Blogs

Book Recommendations