멀티스레드 환경에서 가장 자주 마주치는 문제 중 하나는
"컬렉션을 여러 스레드에서 동시에 접근해도 괜찮은가?"이다.
싱글 스레드 환경에서는 전혀 문제가 없던 코드가
스레드가 하나만 늘어나도 갑자기 예외를 뿜거나,
더 무서운 경우에는 조용히 잘못된 결과를 만들어낸다.
ConcurrentQueue는 이런 문제를 해결하기 위해 등장한 컬렉션이다.
하지만 이걸 단순히 "Thread-safe한 Queue"로만 이해하면
정작 중요한 맥락을 놓칠 수 있기 때문에 이번 글을 통해 정리해보려 한다.
C#의 Queue<T>는 스레드 안전(Thread-safe)하지 않다.
EnqueueDequeue이런 상황이 동시에 발생하면 컬렉션의 내부 상태는 언제든 깨질 수 있다.
그래서 멀티스레드 코드에서는 보통 이런 패턴이 등장한다.
lock(_lock) { queue.Enqueue(item); }
문제는 이 방식이 점점 코드를 갉아먹는다는 점이다.
자료구조 하나 쓰기 위해 동기화를 계속 의식해야 하는 상태
이 자체가 이미 부담이 된다.
ConcurrentQueue<T\>는
동시 접근을 전제로 설계된 Queue이다.
여러 스레드에서 동시에 Enqueue/Dequeue를 호출해도 내부적으로 안전하게 처리된다.
그래서 외부에서 lock을 걸 필요가 없다.
ConcurrentQueue<int> queue = new ConcurrentQueue<int>(); queue.Enqueue(1); if(queue.TryDequeue(out int value)) { // 안전하게 처리 }
여기서 중요한 건
ConcurrentQueue에는 Dequeue()가 없고
항상 Try 계열 메서드만 제공된다는 점이다.
ConcurrentQueue는 API 설계 단계에서부터
이 전제를 깔고 있다.
"지금 이 순간에도 상태는 바뀔 수 있다."
다른 스레드가 이미 데이터를 가져갔을 수도 있고,
내가 확인한 직후 큐의 상태가 달라질 수도 있다.
그래서 ConcurrentQueue는
확실함을 약속하지 않고, 가능성만 제공한다.
이 지점에서 일반 Queue와 사고방식이 갈린다.
ConcurrentQueue를 처음 접했을 때,
다음과 같은 의문이 들었다.
"ConcurrentQueue는 .NET 4.0부터인데,
Unity는 .NET 2.0이라 어차피 못 쓰는거 아니야?"
이말은 과거에는 맞았고, 지금은 틀린 얘기이다.
예전 Unity는
그래서 당시에는
를 사용할 수 없었다.
이 때문에 "Unity에서는 Concurrent 컬렉션을 못 쓴다"는 인식이 오래 남아 있었다.
하지만 지금 Unity는 다르다.
API Compatibility Level이 .NET Standard 2.0 이상이면
System.Collections.Concurrent를 사용할 수 있다.
(현재 Unity에서는 .NET Standard 2.1이 권장된다.)
즉, 현재 Unity에서는 ConcurrentQueue를 문제없이 사용할 수 있다.
Unity에서 멀티스레드를 고민하기 시작하는 순간, 항상 다음과 같은 제약에 부딪힌다.
즉,
백그라운드 스레드에서는 계산만 가능하고,
결과를 화면에 반영하는 작업은
반드시 메인 스레드로 돌아와야 한다.
그래서 Unity에서 멀티스레드를 쓰면
자연스럽게 이런 구조가 만들어진다.
Update에서 하나씩 처리이 지점에서 선택지가 생긴다.
"이 결과를 어떻게 메인 스레드로 넘길 것인가?"
lock으로 감싼 Queue를 직접 만들 수도 있지만,
그 순간부터 동기화 책임은 전부 개발자의 몫이 된다.
그래서 Unity에서는
스레드 간 전달용 버퍼로 ConcurrentQueue를 선택하게 되는 것이다.
ConcurrentQueue는
즉, Unity에서 ConcurrentQueue는
"멀티스레드를 쓰기 시작하면 자연스럽게 만나게 되는 선택지"에 가깝다.
이건 아니라고 할 수 있다.
ConcurrentQueue는 만능이 아니다.
따라서 ConcurrentQueue는
다음과 같은 성격의 작업에 가깝다.
"정확함보다는 안전한 전달이 필요한 경우"
ConcurrentQueue는
단순히 Queue + Thread-safe가 아니다.
그리고 중요한 사실 하나.
과거 Unity는 ConcurrentQueue를 사용할 수 없었지만,
현재 Unity는 ConcurrentQueue를 사용할 수 있다.
문법보다 먼저
사고방식이 바뀌어야 하는 컬렉션,
그게 ConcurrentQueue이다.