반응형
둘 다 “큐”이긴 한데, 쓰레드 안전성이 가장 큰 차이입니다.
1. 네임스페이스 / 기본 성격
- Queue<T>
- System.Collections.Generic
- 단일 쓰레드용 일반 큐
- Enqueue/Dequeue 시에 별도 동기화 없음
- ConcurrentQueue<T>
- System.Collections.Concurrent
- 멀티쓰레드 환경에서 사용하도록 설계된 락-프리(또는 최소 락) 큐
- 여러 쓰레드가 동시에 Enqueue / Dequeue 해도 안전하게 동작
2. 쓰레드 안전성
- Queue<T>
- 여러 쓰레드에서 동시에 접근하면
- 데이터 꼬임
- 예외 발생 (InvalidOperationException 등)
- 프로그램 비정상 동작 가능
- 멀티쓰레드에서 쓰려면 직접 lock 걸어줘야 함
- private readonly object _lock = new object(); private readonly Queue<int> _queue = new Queue<int>(); void Producer(int value) { lock (_lock) { _queue.Enqueue(value); } } bool TryConsume(out int value) { lock (_lock) { if (_queue.Count > 0) { value = _queue.Dequeue(); return true; } } value = default(int); return false; }
- 여러 쓰레드에서 동시에 접근하면
- ConcurrentQueue<T>
- 내부에서 이미 쓰레드 안전하게 동기화 처리
- 여러 쓰레드가 동시에 Enqueue / TryDequeue 해도 무방
- 보통 아래처럼 바로 사용
- private readonly ConcurrentQueue<int> _queue = new ConcurrentQueue<int>(); void Producer(int value) { _queue.Enqueue(value); } bool TryConsume(out int value) { return _queue.TryDequeue(out value); }
3. 메서드 차이
- Queue<T>
- Enqueue(T item)
- T Dequeue() (비어 있으면 예외)
- T Peek() (비어 있으면 예외)
- int Count { get; }
- ConcurrentQueue<T>
- Enqueue(T item)
- bool TryDequeue(out T result) (비어 있으면 false 반환)
- bool TryPeek(out T result)
- int Count { get; }
(멀티쓰레드 환경에서는 “정확한 순간값” 보장까진 아님, 참고용)
예외 대신 bool 리턴 + out 파라미터 패턴을 쓰는 게 특징입니다.
4. 성능 / 용도
- Queue<T>
- 싱글 쓰레드, 또는 lock을 직접 관리할 때 사용
- 멀티쓰레드 고려 없어도 되니 가볍고 단순
- ConcurrentQueue<T>
- 멀티 프로듀서 / 멀티 컨슈머 패턴에서 사용
- lock 최소화/없애도록 설계된 구조라, 다중 쓰레드에서 직접 lock 걸고 Queue<T> 쓰는 것보다 유리한 경우가 많음
- 대신 구조가 복잡하고, Count 등의 연산은 “근사값” 개념으로 보는 게 맞음
5. 언제 뭘 쓰면 되는가
- 단일 쓰레드 또는 큐 접근을 항상 한 쓰레드에서만 하는 경우
- → Queue<T>로 충분
- 여러 쓰레드에서 동시에 Enqueue/Dequeue 하는 경우 (예: Producer/Consumer 작업큐, 로그 큐 등)
- → ConcurrentQueue<T>를 사용하는 것이 안전하고 편함
정리하면:
단일 쓰레드 / 직접 lock 관리 = Queue<T>
멀티쓰레드에서 동시에 사용 = ConcurrentQueue<T>
이렇게 생각하시면 됩니다.
반응형