[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
Mutexlock 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/Syncbounds 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
| Tool | Use case |
|---|---|
| OS threads | CPU-bound work, isolation |
| Tokio tasks | Lots of async I/O waiting |
| Processes | Strong isolation, crash containment |
Further reading
Summary
Takeaways
- thread::spawn: create OS threads
- mpsc::channel: communicate between threads
- Arc: atomically reference-counted sharing
- Mutex: mutual exclusion for mutable shared state
- join: wait for threads to finish