C# 스레드 동기화 완전 가이드
lock (Monitor)
섹션 제목: “lock (Monitor)”가장 일반적인 동기화 방법입니다.
private readonly object _lock = new();
public void Add(int item) { lock (_lock) { _list.Add(item); }}lock은 Monitor.Enter / Monitor.Exit의 문법적 설탕입니다. finally로 항상 해제됩니다.
Monitor.Wait / Pulse — 조건 대기
섹션 제목: “Monitor.Wait / Pulse — 조건 대기”private readonly object _lock = new();private Queue<int> _queue = new();
public void Produce(int item) { lock (_lock) { _queue.Enqueue(item); Monitor.Pulse(_lock); // 대기 중인 스레드 하나 깨움 }}
public int Consume() { lock (_lock) { while (_queue.Count == 0) Monitor.Wait(_lock); // lock 해제 후 대기, 신호 받으면 재획득 return _queue.Dequeue(); }}SemaphoreSlim — 동시 접근 수 제한
섹션 제목: “SemaphoreSlim — 동시 접근 수 제한”private readonly SemaphoreSlim _sem = new(3); // 최대 3개 동시 접근
async Task AccessResourceAsync() { await _sem.WaitAsync(); try { await DoWorkAsync(); } finally { _sem.Release(); }}ReaderWriterLockSlim — 읽기/쓰기 분리
섹션 제목: “ReaderWriterLockSlim — 읽기/쓰기 분리”private readonly ReaderWriterLockSlim _rwLock = new();private Dictionary<string, string> _cache = new();
public string? Get(string key) { _rwLock.EnterReadLock(); try { return _cache.TryGetValue(key, out var v) ? v : null; } finally { _rwLock.ExitReadLock(); }}
public void Set(string key, string value) { _rwLock.EnterWriteLock(); try { _cache[key] = value; } finally { _rwLock.ExitWriteLock(); }}읽기가 압도적으로 많고 쓰기가 드문 캐시, 설정 저장소에 적합합니다.
Interlocked — 락 없는 원자적 연산
섹션 제목: “Interlocked — 락 없는 원자적 연산”private int _counter = 0;
// 원자적 증가int newVal = Interlocked.Increment(ref _counter);
// 비교 후 교환 (CAS)int original = Interlocked.CompareExchange(ref _counter, newValue, expectedValue);
// 원자적 읽기/쓰기 (64비트)long val = Interlocked.Read(ref _longValue);Interlocked.Exchange(ref _longValue, newValue);Mutex — 프로세스 간 동기화
섹션 제목: “Mutex — 프로세스 간 동기화”// 단일 인스턴스 보장using var mutex = new Mutex(true, "Global\\MyApp", out bool createdNew);if (!createdNew) { Console.WriteLine("이미 실행 중"); return;}// 앱 실행...Mutex는 OS 수준 동기화로 프로세스 간 공유 가능합니다. 단일 프로세스 내에서는 lock이 훨씬 가볍습니다.
Volatile과 Memory Barrier
섹션 제목: “Volatile과 Memory Barrier”private volatile bool _running = true;
void Worker() { while (_running) { } // volatile 없으면 최적화로 루프 탈출 안 될 수 있음}
void Stop() { _running = false; }volatile은 캐싱 없이 메모리에서 직접 읽기/쓰기를 보장합니다. Interlocked나 lock보다 약한 보장이지만 단순 플래그에 사용할 수 있습니다.
선택 가이드
섹션 제목: “선택 가이드”| 기법 | 사용 상황 |
|---|---|
lock | 단순 임계 구역 |
Monitor.Wait/Pulse | 조건 대기가 있는 Producer-Consumer |
SemaphoreSlim | 동시 접근 수 제한, async 지원 필요 |
ReaderWriterLockSlim | 읽기 多 / 쓰기 少 |
Interlocked | 단순 카운터, CAS 패턴 |
Mutex | 프로세스 간 동기화, 단일 인스턴스 |
- 가능한 한
lock을 사용하고, 필요할 때 전문화된 기법으로 이동 SemaphoreSlim은async/await와 함께 사용 가능한 유일한 동기화 기본체Interlocked로 카운터/플래그는 락 없이 처리- 데드락 방지: 항상 같은 순서로 락 획득, 최소한의 구간만 락 유지