[2026] Rust Concurrency | Threads, Channels, Arc, and Mutex

[2026] 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

다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

아래 코드는 rust를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

아래 코드는 rust를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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)

아래 코드는 rust를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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: 다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3