1. Swift 동시성 프로그래밍 개요
- 동시성 필요성
- 네트워크 요청, 파일 I/O, 복잡한 계산처럼 메인 스레드를 막는 작업을 백그라운드에서 수행
- UI 반응성을 유지하면서 여러 작업을 병렬 또는 병행 처리
- 동시성 vs 병렬성
- 동시성(Concurrency): 여러 작업이 겉보기상 동시에 진행
- 병렬성(Parallelism): 실제로 여러 CPU 코어에서 동시에 실행
2. Grand Central Dispatch (GCD)
2.1 주요 개념
- DispatchQueue
serial 큐: 한 번에 하나의 작업만 실행
concurrent 큐: 가능한 만큼 여러 작업을 동시에 실행
- 동기(sync) vs 비동기(async)
sync: 호출한 스레드가 작업 완료를 기다림
async: 즉시 반환하고 큐에 작업을 등록만 함
- QoS(Quality of Service)
.userInteractive, .userInitiated, .utility, .background 등 우선순위 지정
- DispatchGroup, DispatchSemaphore, DispatchBarrier
- 그룹화, 동기화, 배리어(장벽) 처리 등에 사용
2.2 사용 예시
let serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async {
print("Serial 작업 1")
}
serialQueue.async {
print("Serial 작업 2")
}
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)
concurrentQueue.sync {
print("Concurrent sync 작업")
}
DispatchQueue.global(qos: .utility).async {
let result = heavyCalculation()
DispatchQueue.main.async {
self.label.text = "\(result)"
}
}
3. OperationQueue vs DispatchQueue
| 구분 | DispatchQueue | OperationQueue |
|---|
| API 레벨 | C 기반(저수준), 간단·가벼움 | Objective-C 기반(NSOperation), 고수준 |
| 작업 단위 | closure 단위 | Operation 서브클래싱 또는 BlockOperation |
| 의존성 | 불가능 | addDependency(_:)로 Task 간 의존성 설정 |
| 취소 | 불가(직접 구현 필요) | cancel() 지원 |
| 최대 동시 작업 수 | 직접 관리 불가 | maxConcurrentOperationCount 설정 가능 |
3.1 언제 무엇을 쓸까?
- DispatchQueue
- 간단한 비동기·동기 처리, 속도와 가벼움을 우선할 때
- OperationQueue
- Task 간 의존성, 취소, 상태 관리를 체계적으로 하고 싶을 때
4. 동시성 프로그래밍 문제와 해결 방법
4.1 Race Condition (경쟁 상태)
- 문제: 여러 스레드가 같은 자원(변수, 배열 등)에 동시에 접근·수정
- 증상: 예측 불가능한 값, 데이터 손상
- 해결
- serial queue로 접근 직렬화
DispatchSemaphore, NSLock 등 락 사용
- Swift 5.5 이후 actor 도입으로 안전한 상태 관리
actor Counter {
private var value = 0
func increment() { value += 1 }
func get() -> Int { value }
}
let counter = Counter()
Task {
await counter.increment()
print(await counter.get())
}
4.2 Deadlock (교착 상태)
4.3 Starvation (기아 현상)
- 문제: 낮은 우선순위 작업이 실행 기회를 얻지 못함
- 해결
- QoS 설정 적절히 조정
- 중요한 작업이 과도한 리소스를 차지하지 않도록 설계