본문으로 건너뛰기
Previous
Next
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



자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Rust concurrency tutorial: std::thread, mpsc channels, Arc and Mutex, parallel sums, pitfalls, Send/Sync, and when to us… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Rust, Concurrency, Threads, Mutex, Channel, Arc 등으로 검색하시면 이 글이 도움이 됩니다.