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는 다음 라인 실행을 차단하지 않는다.