애플리케이션 성능을 높이기 위해 iOS 개발자들은 종종 동시성 처리를 고려하게 됩니다. 이 글에서는 멀티스레딩의 기초 개념부터 실제 코드 예제, 그리고 실무에서 주의해야 할 핵심 사항까지 체계적으로 정리합니다.
1. 기본 개념 이해
프로세스와 스레드
- 하나의 앱은 하나 이상의 프로세스로 실행되며, 각 프로세스는 최소 하나 이상의 스레드를 가집니다.
- 스레드는 프로세스 내부의 실행 단위로, 메모리를 공유할 수 있어 효율적인 자원 활용이 가능합니다.
- 프로세스 간에는 직접적인 메모리 공유가 불가능하며, IPC(Inter-Process Communication) 기법을 사용해야 합니다.
2. iOS 멀티스레딩 기술 개요
iOS에서는 세 가지 주요 방식으로 동시성을 구현할 수 있습니다:
- NSThread: 가장 기본적이지만, 수동으로 스레드 생명주기와 동기화를 관리해야 해 부담이 큽니다.
- NSOperation: 작업 단위(Operation)를 큐(OperationQueue)에 등록하여 자동으로 관리됩니다. 추상 클래스인
NSBlockOperation등을 통해 유연하게 작업을 구성할 수 있습니다. - GCD (Grand Central Dispatch): 시스템이 스레드 풀을 관리해주므로 개발자는 작업만 정의하면 됩니다. 가장 권장되는 방식입니다.
GCD 디스패치 큐 종류
- Serial Queue: 작업을 순차적으로 실행 —
DISPATCH_QUEUE_SERIAL - Concurrent Queue: 병렬로 작업 실행 —
DISPATCH_QUEUE_CONCURRENT - Global Queue: 시스템이 제공하는 전역 병렬 큐 — 우선순위 설정 가능
- Main Queue: UI 업데이트용 메인 스레드 큐 — 반드시 여기서 UI 조작
3. 실전 코드 예제
#define BACKGROUND_Q dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
#define MAIN_Q dispatch_get_main_queue()
(1) 비동기 작업 후 UI 갱신
dispatch_async(BACKGROUND_Q, ^{
// 네트워크 요청 또는 무거운 계산 수행
dispatch_sync(MAIN_Q, ^{
// UI 갱신: 반드시 메인 큐에서 실행
self.label.text = @"로딩 완료";
});
});
(2) 그룹 작업 완료 대기
- (void)downloadImages {
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 5; i++) {
dispatch_group_async(group, BACKGROUND_Q, ^{
[self loadImageAtIndex:i];
});
}
dispatch_group_notify(group, MAIN_Q, ^{
// 모든 이미지 다운로드 완료 후 실행
[self refreshCollectionView];
});
}
(3) 배리어(Barrier)를 이용한 안전한 쓰기
- (void)updateSharedData {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.app.dataQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{ NSLog(@"읽기 작업 1"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"읽기 작업 2"); });
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"쓰기 작업 - 다른 작업 모두 대기");
// 데이터 갱신 로직
});
dispatch_async(concurrentQueue, ^{ NSLog(@"읽기 작업 3"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"읽기 작업 4"); });
}
(4) 반복 작업 실행
dispatch_apply(5, BACKGROUND_Q, ^(size_t idx) {
NSLog(@"반복 작업 %zu", idx);
// 병렬로 5회 실행됨
});
4. 실무에서 주의할 점
멀티스레딩 환경에서는 특히 데드락(Deadlock)을 경계해야 합니다. 다음 네 가지 조건이 동시에 만족되면 발생할 수 있습니다:
- 상호배제: 자원을 한 번에 하나의 스레드만 사용 가능
- 점유와 대기: 자원을 점유한 상태에서 다른 자원을 요청
- 비선점: 자원을 강제로 빼앗을 수 없음
- 순환 대기: 여러 스레드가 서로의 자원을 기다리는 순환 구조
예방 방법:
- 자원 접근 순서를 일관되게 유지하세요. (예: A → B → C 순서로 항상 접근)
- 락을 최대한 짧게 유지하고, 중첩된 락은 피하세요.
- 타임아웃이나 데드락 감지 메커니즘을 도입하는 것도 고려해보세요.