[GCD] Grand Central Dispatch

Posted by Sung Kyungmo on September 3, 2019

GCD는 병렬 프로그래밍(Concurrency Programming) 혹은 멀티 스레딩(Multi Threading)작업을 수행하는 API입니다. 즉, 어느 작업을 어디서 수행할지, 순차적으로 하나씩 처리할지 여러개를 동시에 처리할지 Sync로 할지 Async로 할지 등을 처리하는 것이 바로 GCD API입니다. 상황에 맞게 적절한 Queue와 QoS를 선택해 실행 효율을 높일 수 있습니다.


Dispatch Queue


GCD는 Thread Safe 한 Dispatch Queue를 제공하여 Multi-threading 작업을 수행하도록 해줍니다. 모든 Dispatch Queue은 선입선출(FIFO)데이터 구조입니다. 따라서 task는 항상 추가된 순서대로 시작됩니다.

Serial(main)

Serial queues는 큐에 추가된 순서대로 하나씩 처리하는 직렬 큐 입니다. 하나의 Task가 완료되어야 다음 Task를 처리합니다.

Serial-Queue

UI와 관련된 모든 event가 Main Thread에 붙어있기(attach)때문에, 모든 UI 작업은 Main Queue에서 수행해야 합니다.


Concurrent(global)

Concurrent queues은 동시에 여러개의 작업들을 실행하는 병렬 큐 입니다. Task는 큐에 추가된 순서대로 실행됩니다. 한번에 실행가능한 Task의 수는 가변적이며 시스템 조건에 따라 다릅니다. Global Queue는 작업의 우선 순위를 위한 QOS (Quality of Service)를 제공합니다.

Concurrent-Queue

전역 동시 큐(global concurrent queues)에 작업을 보낼때, 직접 우선순위를 지정하지 않습니다. 대신에, QOS (Quality of Service) 클래스 프로퍼티를 지정합니다: 이는 작업의 중요성을 나타내고 GCD가 작업에 부여할 우선순위를 결정하는 것을 도와줍니다.


Sync, Async


Sync(동기)

큐에 등록한 스레드가 작업이 끝나고 나서 다음 큐를 순차적으로 실행합니다.

Async(비동기)

큐에 등록한 스레드가 작업이 끝날때까지 대기하지않고 다음 큐를 순차적으로 동시에 실행합니다. 작업의 종료는 순차적이지 않고 작업마다 다를 수 있습니다. 차이점은 하나의 작업이 완료될때까지 기다렸다가 다음 작업을 실행하느냐 입니다. Sync 같은 경우 오래 걸리거나 언제 완료될 지 불확실한 경우에 사용하게 되면 아무것도 할 수 없기 때문에 Async를 사용해야 합니다. 어떤 큐에 동기 비동기를 사용하는지에 따라 다음과 같이 사용할 수 있습니다.

  • Serial
    • sync
    • async
  • Concurrent
    • sync
    • async


Dispatch Queue 사용


DispatchQueue 생성

1
2
3
4
5
6
7
8
9
10
11
// Serial Queue
DispatchQueue.main.sync { } // CRASH!
DispatchQueue.main.async { }
DispatchQueue(label: "com.CustomSerialQueue").sync { }
DispatchQueue(label: "com.CustomSerialQueue").async { }

// Concurrent Queue
DispatchQueue.global().sync { }
DispatchQueue.global().async { }
DispatchQueue(label: "com.CustomConcurrentQueue", attributes: .concurrent).sync { }
DispatchQueue(label: "com.CustomConcurrentQueue", attributes: .concurrent).async { }

DispatchQueue.main.sync 를 사용하게되면 에러가 나게됩니다. 그 이유는 메인 큐에서 작업중에 메인 큐에 sync를 하여 블락을 하면 데드락(교착상태)에 빠지게 됩니다. Serial 큐에 동기작업을 할 경우에는 main이 아닌 커스텀 큐를 직접 만들어 사용하면 됩니다.

Serial queue - Sync

큐의 작업이 종료될때까지 다른 작업이 block되므로 한 작업이 모두 끝나야 다른작업이 순차적으로 실행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
let serialQueue = DispatchQueue(label: "serialQueue")

serialQueue.sync {
    for i in 0...3{
        print("\(i) [serial_sync_1]")
    }
    print("--------------------------")
}

serialQueue.sync {
    for i in 0...5{
        print("\(i) [serial_sync_1]")
    }
    print("--------------------------")
}

for i in 0...5{
    print(i)
}


======== Result ========

0 [serial_sync_1]
1 [serial_sync_1]
2 [serial_sync_1]
3 [serial_sync_1]
--------------------------
0 [serial_sync_2]
1 [serial_sync_2]
2 [serial_sync_2]
3 [serial_sync_2]
--------------------------
0
1
2
3

Serial queue - Async

비동기로 큐를 실행하여 다른 작업은 계속 처리되지만 직렬큐이므로 큐에 들어간 작업은 순서대로 처리됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
let serialQueue = DispatchQueue(label: "serialQueue")

serialQueue.async {
    for i in 0...3{
        print("\(i) [serial_async_1]")
    }
    print("--------------------------")
}


serialQueue.async {
    for i in 0...3{
        print("\(i) [serial_async_2]")
    }
    print("--------------------------")
}

for i in 0...5 {
    print(i)
}


======== Result ========

0
0 [serial_async_1]
1
1 [serial_async_1]
2
2 [serial_async_1]
3
3 [serial_async_1]
4
--------------------------
0 [serial_async_2]
5
1 [serial_async_2]
2 [serial_async_2]
3 [serial_async_2]
--------------------------

Concurrent queue - Sync

시리얼 큐에서 sync와 같이 한 작업이 모두 끝나야 다른작업이 순차적으로 실행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
DispatchQueue.global().sync {
    for i in 0...3{
        print("\(i) [global_sync_1]")
    }
    print("--------------------------")
}

DispatchQueue.global().sync {
    for i in 0...3{
        print("\(i) [global_sync_2]")
    }
    print("--------------------------")
}

for i in 0...3 {
    print(i)
}


======== Result ========

0 [global_sync_1]
1 [global_sync_1]
2 [global_sync_1]
3 [global_sync_1]
--------------------------
0 [global_sync_2]
1 [global_sync_2]
2 [global_sync_2]
3 [global_sync_2]
--------------------------
0
1
2
3

Concurrent queue - Async

병렬로 작업을 동시에 처리하기 때문에 무엇이 먼저 시작되고 끝날지는 알 수 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
DispatchQueue.global().async {
    for i in 0...3{
        print("\(i) [global_async_1]")
    }
    print("--------------------------")
}

DispatchQueue.global().async {
    for i in 0...3{
        print("\(i) [global_async_2]")
    }
    print("--------------------------")
}

for i in 0...3 {
    print(i)
}


======== Result ========

0
0 [global_async_2]
0 [global_async_1]
1
1 [global_async_2]
1 [global_async_1]
2
2 [global_async_1]
3
2 [global_async_2]
3 [global_async_1]
3 [global_async_2]
--------------------------
--------------------------

참고자료

https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2
https://zeddios.tistory.com/513
https://magi82.github.io/gcd-01/