iOS에서 멀티스레딩 구현과 실무 적용 가이드

애플리케이션 성능을 높이기 위해 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 순서로 항상 접근)
  • 락을 최대한 짧게 유지하고, 중첩된 락은 피하세요.
  • 타임아웃이나 데드락 감지 메커니즘을 도입하는 것도 고려해보세요.

태그: gcd Swift iOS-Concurrency

10월 5일 08:39에 게시됨