Rust Concurrency | Threads, Channels, Arc, and Mutex

Rust Concurrency | Threads, Channels, Arc, and Mutex

이 글의 핵심

Learn Rust concurrency: spawning threads, message passing with channels, shared state with Arc and Mutex, parallel aggregation patterns, and production tips.

Introduction

Rust supports concurrent programming while preserving memory safety in safe code.


1. Threads

Basic threading

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: {}", i);
        thread::sleep(Duration::from_millis(100));
    }
    
    handle.join().unwrap();
}

Moving data into a thread

use std::thread;

fn main() {
    let v = vec![1, 2, 3];
    
    let handle = thread::spawn(move || {
        println!("vector: {:?}", v);
    });
    
    handle.join().unwrap();
    
    // `v` was moved into the closure and cannot be used here
}

2. Channels

Basic channel

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

fn main() {
    let (tx, rx) = mpsc::channel();
    
    thread::spawn(move || {
        let val = String::from("hello");
        tx.send(val).unwrap();
    });
    
    let received = rx.recv().unwrap();
    println!("received: {}", received);
}

Multiple messages

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_millis(100));
        }
    });
    
    for received in rx {
        println!("received: {}", received);
    }
}

Multiple senders

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

fn main() {
    let (tx, rx) = mpsc::channel();
    let tx2 = tx.clone();
    
    thread::spawn(move || {
        tx.send(String::from("thread 1")).unwrap();
    });
    
    thread::spawn(move || {
        tx2.send(String::from("thread 2")).unwrap();
    });
    
    for received in rx {
        println!("received: {}", received);
    }
}

3. Arc<T> and Mutex<T>

Mutex (mutual exclusion)

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);
    
    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }  // lock released here
    
    println!("m = {:?}", m);
}

Arc + Mutex

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
}

4. Hands-on example

Parallel sum

use std::thread;
use std::sync::{Arc, Mutex};

fn parallel_sum(numbers: Vec<i32>) -> i32 {
    let chunk_size = numbers.len() / 4;
    let numbers = Arc::new(numbers);
    let result = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    
    for i in 0..4 {
        let numbers = Arc::clone(&numbers);
        let result = Arc::clone(&result);
        
        let handle = thread::spawn(move || {
            let start = i * chunk_size;
            let end = if i == 3 { numbers.len() } else { (i + 1) * chunk_size };
            
            let sum: i32 = numbers[start..end].iter().sum();
            
            let mut total = result.lock().unwrap();
            *total += sum;
        });
        
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    *result.lock().unwrap()
}

fn main() {
    let numbers: Vec<i32> = (1..=1000).collect();
    let sum = parallel_sum(numbers);
    println!("sum: {}", sum);  // 500500
}

Production notes

Chunked parallel sum (stdlib only)

std::mpsc::Receiver is not cloneable, so a “many workers, one queue” pattern is awkward with raw channels alone. Pre-chunk the data and give each thread its own slice instead:

use std::thread;

fn parallel_sum(nums: Vec<i32>, workers: usize) -> i32 {
    assert!(workers > 0);
    let chunk_size = (nums.len() + workers - 1) / workers;
    let chunks: Vec<Vec<i32>> = nums
        .chunks(chunk_size.max(1))
        .map(|c| c.to_vec())
        .collect();

    let handles: Vec<_> = chunks
        .into_iter()
        .map(|chunk| {
            thread::spawn(move || chunk.iter().copied().sum::<i32>())
        })
        .collect();

    handles.into_iter().map(|h| h.join().unwrap()).sum()
}

fn main() {
    let nums: Vec<i32> = (1..=10_000).collect();
    let total = parallel_sum(nums, 4);
    println!("sum: {}", total);
}

Common mistakes

  • Holding a Mutex lock across I/O, starving other workers.
  • Sharing data across threads without Arc (or another safe mechanism).
  • Deadlocks when locking multiple mutexes in inconsistent order.

Caveats

  • A poisoned lock() often means another thread panicked while holding the lock.
  • Send / Sync bounds often show up first in closure captures—learn what they mean.

In production

  • CPU-bound work: often rayon; I/O-heavy work: tokio (or another async runtime).
  • Prefer message passing and minimal shared mutable state.

Comparison

ToolUse case
OS threadsCPU-bound work, isolation
Tokio tasksLots of async I/O waiting
ProcessesStrong isolation, crash containment

Further reading


Summary

Takeaways

  1. thread::spawn: create OS threads
  2. mpsc::channel: communicate between threads
  3. Arc: atomically reference-counted sharing
  4. Mutex: mutual exclusion for mutable shared state
  5. join: wait for threads to finish

Next steps


  • C++ memory model
  • C++ multithreading basics
  • C++ vs Rust: full comparison
  • Java multithreading | Thread, Runnable, Executor
  • C++ atomics