Concurrency and Multithreading in Rust: Safety and Performance

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: Writing Safe and Fast Code



Modern programs rarely run in single-threaded mode. Request handling, file operations, network tasks — all of this requires parallel execution of tasks. Rust offers a unique approach to concurrency: it guarantees memory safety at compile time, preventing data races. In this article, we will explore how to work with threads, channels, mutexes, and asynchrony in Rust.



1. Basics of Multithreading: Creating and Managing Threads



In Rust, threads are created using the standard library std::thread. Each thread executes independently, but the compiler strictly monitors variable lifetimes.



Creating a Thread

use std::thread;use std::time::Duration;

fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("Thread: {}", i); thread::sleep(Duration::from_millis(100)); } });

for i in 1..5 { println!("Main thread: {}", i); thread::sleep(Duration::from_millis(100)); }

handle.join().unwrap(); // Wait for the thread to finish}


Capturing Ownership in Closures

If a thread uses variables from an outer scope, ownership must be transferred using move:

let data = vec![1, 2, 3];

let handle = thread::spawn(move || { println!("Data: {:?}", data);});

handle.join().unwrap();// println!("{:?}", data); // Error! data has been moved


2. Data Sharing: Mutexes and Locks



For safe access to shared data from multiple threads, Rust uses Mutex in conjunction with Arc (atomic reference counter).



Example with Mutex and Arc

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


Important: Mutex is automatically unlocked when it goes out of scope. This prevents deadlocks at the compiler level.



RwLock — for Frequent Reads

If data is rarely modified but frequently read, use RwLock:

use std::sync::RwLock;

let lock = RwLock::new(5);

{ let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); // Can read simultaneously println!("{} {}", r1, r2);}

{ let mut w = lock.write().unwrap(); *w += 1; // Writing blocks reading}


3. Channels: Message Passing Between Threads



Rust supports the actor model through channels (std::sync::mpsc). This allows threads to exchange data without shared memory.



Example with a Channel

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

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

thread::spawn(move || { let vals = vec!["hello", "from", "the", "thread"]; for val in vals { tx.send(val).unwrap(); thread::sleep(std::time::Duration::from_secs(1)); } });

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


Multiple Senders

Using mpsc::Sender, you can clone the sender for multiple threads:

let (tx, rx) = mpsc::channel();let tx1 = tx.clone();

thread::spawn(move || { tx1.send("Thread 1").unwrap(); });thread::spawn(move || { tx.send("

Blogs

Book Recommendations