Swift에서 스레드 안전성을 보장하기 위해 종종 GCD(Grand Central Dispatch) 및 큐(DispatchQueue), NSLock, 그리고 Swift 5.5부터의 현대적 메커니즘인 actor를 사용합니다.
주요 아이디어 - 변경 가능한 데이터에 대한 접근은 반드시 순차 큐를 통해 엄격하게 이루어지거나 동기화기(잠금)을 사용해야 합니다.
GCD 예제:
class ThreadSafeArray<Element> { private var array: [Element] = [] private let queue = DispatchQueue(label: "com.example.arrayQueue", attributes: .concurrent) func append(_ item: Element) { queue.async(flags: .barrier) { self.array.append(item) } } func get(index: Int) -> Element? { var result: Element? queue.sync { result = self.array.indices.contains(index) ? self.array[index] : nil } return result } }
현대적 접근: Actors (Swift 5.5+):
actor SafeCounter { private var value = 0 func increment() { value += 1 } func get() -> Int { return value } }
두 접근 방식 모두 데이터 경쟁을 피하고 상태 일관성을 유지할 수 있게 해줍니다.
질문:
만약 Main Queue 내부에서
DispatchQueue.sync를 사용한다면, 무슨 일이 발생하며 그 이유는 무엇인가요?
답변: 교착 상태(Deadlock)가 발생할 것입니다. 이는 Main Queue가 이미 작업을 수행 중이며 같은 큐에서 동기 작업의 완료를 기다리고 있기 때문입니다. 따라서 현재 작업이 완료되지 않는 한 대기 중인 다음 작업을 처리할 수 없게 됩니다.
예제:
DispatchQueue.main.sync { // 교착 상태: 이 줄은 절대 실행되지 않습니다. }
이야기
프로젝트에서 동기화 없이 여러 스레드에서 동시에 접근하는 변경 가능한 컬렉션을 사용했습니다. 이로 인해 데이터 경쟁으로 인한 애플리케이션 충돌 및 잡히지 않는 버그가 발생했으며, 특정기간마다 요소가 손실되거나 배열의 경계를 초과하는 경우가 발생했습니다.
이야기
개발자가 런타임에서 Main Queue에 대해 DispatchQueue.sync를 잘못 사용하여 교착 상태가 발생하고 업데이트 후 사용자 UI가 완전히 중단되었습니다. 긴급한 수정 및 출시 롤백이 필요했습니다.
이야기
NSLock을 사용하여 클래스를 스레드 안전하게 만들려고 시도했지만, 메소드의 모든 종료 경로에 대해 lock/unlock을 구현하는 것을 잊어버려 교착 상태의 잠재성과 애플리케이션 성능에 심각한 문제를 일으켰습니다.