본문으로 건너뛰기 Rust Concurrency | Threads, Channels, Arc, and Mutex

Rust Concurrency | Threads, Channels, Arc, and Mutex

Rust Concurrency | Threads, Channels, Arc, and Mutex

이 글의 핵심

Rust concurrency tutorial: std::thread, mpsc channels, Arc and Mutex, parallel sums, pitfalls, Send/Sync, and when to use rayon or Tokio in production.

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



Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Rust concurrency tutorial: std::thread, mpsc channels, Arc and Mutex, parallel sums, pitfalls, Send/Sync, and when to us…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Rust, Concurrency, Threads, Mutex, Channel, Arc.