GCD 사용법

GCD

GCD는 Grand Central Dispatch의 약자로 병렬 작업을 관리하는 저수준의 API다. GCD로 무거운 작업을 백그라운드로 돌려 앱의 반응성을 향상시켜 궁극적으로 앱의 퍼포먼스를 향상시킬 수 있다. GCD를 이해하기전 몇가지를 꼭 알아야 한다.

프로세스

프로세스란 실행되고 있는, 실행중인 프로그램이다. 프로그램은 실행되기 전까지는 명령어와 데이터의 묶음일뿐이지만, 이 프로그램을 실행시켜 메모리 상에 올라오게 되면 프로세스가 된다. 일반적으로 CPU는 한번에 하나의 프로세스만 관리하지만, 멀티 태스킹이라는 눈속임으로 프로세스가 동시에 실행되는 것처럼 할 수 있다.

스레드

스레드란 프로세스안에서 직접 일하는 일꾼이라고 볼 수 있다. 기본적으로 프로세스당 최소 1개의 스레드를 가지고 있고, 이 스레드를 메인 스레드라 한다.

멀티 프로세스와 멀티 스레드

멀티 스레드는 멀티 프로세스보다 많은 이점을 가진다. Context Switching시 스레드는 프로세스 공통 저장 공간인 Heap 영역을 통해 서로 통신하기 때문에 시간과 자원 손실을 줄인다. 반면 프로세스간에는 RAM과 CPU 사이의 캐쉬 메모리에 대한 데이터까지 초기화되므로 상당한 부담이 발생한다. 그러나 멀티 스레드는 동기화에 대한 문제에 직접 신경을 써야만 한다.

Concurrency VS Parallelism

iOS에서 애플리케이션 혹은 프로세스는 하나 이상의 스레드로 구성되어 있다. OS 스케줄러가 각각의 스레드를 독립적으로 관리한다. 각각의 스레드는 동시에 실행될 수 있지만 이런 상황을 결정하고 왜 동시에 실행하는지는 전적으로 OS에 달려있다. 싱글코어에서는 시분할이라고 불리는 방식을 통해 concurrency를 구현하며 Context Switch로 스레드를 옮긴다. 반대로 멀티코어에서는 병렬처리를 통해 여러 스레드를 동시에 실행한다.

그렇기 때문에 Concurrency와 Parallelism은 완벽히 다른 것이다. Parallelism은 Concurrency를 필요로 하지만 Concurrency가 Parallelism을 보장하진 않는다.

Queue

GCD는 FIFO 방식으로 실행된다. Dispatch Queue는 thread-safe이므로 여러 스레드에 동시에 접근할 수 있다. Dispatch Queue는 serial 혹은 concurrent가 될 수 있다. Serial Queue는 한 번에 한 가지만 실행되는 것을 보장한다. 즉 지금 일을 끝내지 않고서는 다음 일을 하지 않는다.

Concurrent Queue는 현재 일이 끝나지 않더라도 다른 일을 시작할 수 있다. 때문에 먼저한 일이 제일 늦게 끝날 수도 있지만 어쨌든 실행되는 순서는 존재한다. GCD는 3종류의 큐를 제공한다.

  • Main queue: 메인 스레드에서 작동하며 serial queue다.
  • Global queue: 전체 시스템이 공유하는 concurrent queue이다.
    • Global queue의 경우 QoS를 통해 중요도를 지정하고 순서를 변경할 수 있다. (userInteractive > userInitiated > default > utility > background > unspecified)
  • Custom queue: 직접 만드는 queue이며 serial 혹은 concurrent로 만들 수 있고 실제로는 global queue에서 끝나게 된다.

동기와 비동기

GCD를 이용하면 동기식과 비동기식 어느쪽으로든 작업할 수 있다. DispatchQueue.sync(execute:)를 통해 동기적으로 스케줄링 할 수 있다. DispatchQueue.async(execute:)를 통해 비동기적으로 스케줄링 할 수 있다. 비동기방식은 완료를 기다리지 않기 때문에 현재 실행중인 스레드가 다음 함수로 진행하는 것을 차단하지 않는다.

Main vs Global

Main은 메인 스레드에서 작동하고 Serial Queue다. 따라서 한 번에 한가지만 실행된다. Main 스레드는 UI를 위한 스레드이기 때문에 되도록 UI 업데이트시에만 사용한다. 무거운 작업이나 UI 업데이트가 없는 로직의 경우 Concurrent Queue인 Global을 사용하면 된다. (QoS 지정)

Sync vs Async

Async는 다음 라인 실행을 차단하지 않는다 라는 것만 기억해두면 된다.

코드

Sync

func a() {
    DispatchQueue.global().sync {
        for i in 0...10 {
            print(i)
        }
    }
    print("b")
}

// 결과
0
1
2
3
4
5
6
7
8
9
10
b

Async

func a() {
    DispatchQueue.global().async {
        for i in 0...10 {
            print(i)
        }
    }
    print("b")
}

// 결과
b
0
1
2
3
4
5
6
7
8
9
10

QoS 지정

func a() {
    DispatchQueue.global(qos: .userInteractive).async {
        for i in 0...10 {
            print(i)
        }
    }
    print("b")
}

// 결과
0
b
1
2
3
4
5
6
7
8
9
10

결론

UI 업데이트는 무조건 Main 스레드에서, 무거운 작업은 background에서. 순서가 중요한 경우 sync를 사용, async는 다음 라인 실행을 차단하지 않는다.