동시성: 타입 시스템이 보장하는 스레드 안전성
학습 목표: Rust가 어떻게 컴파일 타임에 데이터 경합(Data Race)을 원천 차단하는지 배웁니다. C#의 관례적인
lock방식과 Rust의Arc<Mutex<T>>방식을 비교하고, **Send**와Sync트레이트를 통해 "공포 없는 동시성(Fearless Concurrency)"을 실천하는 법을 익힙니다.
1. C# vs Rust: 스레드 안전의 철학 차이
C#에서 스레드 안전성은 개발자의 기억력에 의존합니다. lock을 잊어버리면 런타임에 데이터가 꼬이지만, Rust는 데이터를 공유하려면 반드시 안전한 구조(Arc, Mutex 등)를 갖춰야만 컴파일이 가능합니다.
| 비교 항목 | C# (Lock-based) | Rust (Ownership-based) |
|---|---|---|
| 안전성 보장 | 런타임/개발자 책임 | 컴파일 타임 강제 |
| 데이터 경합 | 발생 가능 (디버깅 지옥) | 원천 차단 (컴파일 에러) |
| 공유 방식 | 수동 lock(obj) { ... } | Arc<Mutex<T>> / 메시지 패싱 |
| 성능 오버헤드 | 가상 디바이스, 컨텍스트 스위칭 | 제로 비용 추상화 (Atomic 활용) |
2. 핵심 도구: Arc<Mutex<T>>
여러 스레드가 하나의 데이터를 안전하게 읽고 쓰기 위한 가장 표준적인 조합입니다.
Arc<T>: 원자적 참조 횟수 계산기. 여러 주인(스레드)이 데이터를 가질 수 있게 합니다.Mutex<T>: 데이터를 '상호 배제' 락으로 감쌉니다. 락을 얻어야만 데이터에 접근할 수 있습니다.
#![allow(unused)] fn main() { let counter = Arc::new(Mutex::new(0)); for _ in 0..10 { let counter = Arc::clone(&counter); thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; // 락을 얻은 상태에서만 수정 가능 }); } }
3. 메시지 패싱 (Channels)
"메모리를 공유해서 통신하지 말고, 통신해서 메모리를 공유하라"는 철학입니다. 채널을 통해 데이터를 주고받으면 락 없이도 안전하게 협업할 수 있습니다.
#![allow(unused)] fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send("데이터 전송").unwrap(); }); let msg = rx.recv().unwrap(); }
4. Send와 Sync: 스레드 안전의 증표
Rust 컴파일러가 타입을 검사할 때 사용하는 마커 트레이트입니다.
Send: 소유권을 다른 스레드로 넘겨도 안전한 타입 (이동 가능).Sync: 여러 스레드에서 참조(&T)를 공유해도 안전한 타입.
💡 실무 팁: rayon으로 병렬화 뚝딱!
복잡한 스레드 관리 없이 리스트 처리를 병렬로 하고 싶다면 rayon 크레이트를 쓰세요. .iter()를 .par_iter()로 바꾸기만 해도 모든 CPU 코어를 활용해 데이터를 처리합니다. 빌림 검사기가 안전을 보장하므로 안심하고 지를 수 있습니다.