본문 바로가기

이상/iOS

[iOS] DispatchQueue - Serial, Concurrent

반응형

이전 글에서 GCD에 대해 정리했다.

 

이번에는 DispatchQueue가 어떻게 작동되는지 보자.

 

 

1. Serial Queue

 

DispatchQueue의 Serial Queue는 Queue에 담긴 작업을 순차적으로 실행하며,

 

Main Queue 또는 Custom Queue로 사용할 수 있다.

 

 

Main Queue는 현재 프로세스의 Main Thread와 연결된 DispatchQueue다.

 

서버로부터 받아온 데이터를 화면에 뿌릴 때

(UITableView나 UICollectionView의 경우 reloadData())

 

화면 갱신에 대한 소스가 담긴 클로저를 Main Queue로 보내 처리하는 경우가 많다.

 

 

Main Queue - async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print("1")
DispatchQueue.main.async {
    print("🟡ㄱ")
 
}
print("2")
DispatchQueue.main.async {
    print("🟡ㄴ")
 
}
print("3")
 
DispatchQueue.main.async {
    print("🟡ㄷ")
 
}
cs

 

결과

[실행 순서]

print("1") 실행

print("🟡ㄱ") Main Thread의 Queue에 담는다.

print("2") 실행

print("🟡ㄴ") Main Thread의 Queue에 담는다.

print("3") 실행

print("🟡ㄷ") Main Thread의 Queue에 담는다.

전체 runloop가 끝나면 Queue에 들어가 있는 task들이 순차적으로 실행된다.

 

Main Queue는 Main Thread에서 돌아가기 때문에 다른 DispatchQueue들과는 다르다.

 

DispatchQueue가 속해있는 runloop가 다 실행된 후(after the current runloop completes)에 Queue가 실행된다.

 

때문에 Main Queue가 항상 마지막에 실행된다. (참고)

(+ 21.03.15 추가

viewDidLoad, viewWillAppear과 같은 메소드나

UI 관련 이벤트들은 Main Thread에서 실행되기 때문에

- 바꿔 말해 해당 이벤트들이 Main Queue에 이미 들어가 있기 때문에

DispatchQueue.main.async로 추가되는 task들은

그 이후에 Queue에 들어가게 되므로 가장 마지막에 실행된다.)

 

 

Custom 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
var serialQueue = DispatchQueue(label: "SerialQueue")
print("1")
serialQueue.async {
    print("🔵A")
}
print("2")
serialQueue.async {
    print("🔵B")
}
print("3")
serialQueue.async {
    print("🔵C")
}
print("4")
serialQueue.async {
    print("🔵D")
}
print("5")
serialQueue.async {
    print("🔵E")
}
print("6")
serialQueue.async {
    print("🔵F")
}
cs

 

결과

[실행 순서]

print("1") 실행

print("🔵A") Global Queue에 담는다.

print("2") 실행

print("🔵B") Global Queue에 담는다.

print("3") 실행

print("🔵C")Global Queue에 담는다.

print("4") 실행

print("🔵D")Global Queue에 담는다.

print("5") 실행

print("🔵E")Global Queue에 담는다.

print("6") 실행

print("🔵F")Global Queue에 담는다.

Global Queue에 담긴 작업들은 언제 실행될 지는 모르지만 담긴 순서대로 실행된다.

 

default로 만든 Custom Queue이기 때문에 Serial Queue이다.

 

하지만 Main Queue와는 다르게 마지막에 실행되는게 아니라 독립적으로 실행된다.

 

ABCDEF의 순서는 보장되지만

 

123456이 찍히는 동안 사이사이에 찍힐 수 있다.

 

 

Main, Custom 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
var serialQueue = DispatchQueue(label: "SerialQueue")
print("1")
DispatchQueue.main.async {
    print("🟡ㄱ")
}
print("2")
serialQueue.async {
    print("🔵A")
}
print("3")
DispatchQueue.main.async {
    print("🟡ㄴ")
}
print("4")
serialQueue.async {
    print("🔵B")
}
print("5")
DispatchQueue.main.async {
    print("🟡ㄷ")
}
print("6")
serialQueue.async {
    print("🔵C")
}
cs

 

결과

[실행 순서]

print("1") 실행

print("🟡ㄱ") Main Queue에 담는다.

print("2") 실행

print("🔵A") Global Queue에 담는다.

print("3") 실행

print("🟡ㄴ") Main Queue에 담는다.

print("4") 실행

print("🔵B") Global Queue에 담는다.

print("5") 실행

print("🟡ㄷ") Main Queue에 담는다.

print("6") 실행

print("🔵C")Global Queue에 담는다.

Global Queue에 담긴 작업들은 언제 실행될 지는 모르지만

각각 2 이후, 4 이후, 6 이후에 실행되며 담긴 순서대로 실행된다.

전체 runloop가 끝나면 Main Queue에 들어가 있는 task들이 순차적으로 실행된다.

 

 

 Custom Queue - sync

1
2
3
4
5
6
7
8
9
10
11
12
13
var serialQueue = DispatchQueue(label: "SerialQueue")
print("1")
serialQueue.sync {
    print("🔵A")
}
print("2")
serialQueue.sync {
    print("🔵B")
}
print("3")
serialQueue.sync {
    print("🔵C")
}
cs

 

결과

[실행 순서]

print("1") 실행

print("🔵A") Global Queue에 담고 실행.

print("🔵A")가 끝나면 print("2") 실행.

print("🔵B") Global Queue에 담고 실행.

print("🔵B")가 끝나면 print("3") 실행.

print("🔵C") Global Queue에 담고 실행.

 

sync이므로 순서대로 실행된다.

 

 

 

2. Concurrent Queue

 

DispatchQueue의 Concurrent Queue는 Queue에 담긴 작업을 동시에 실행하며,

 

Global Queue 또는 Custom Queue로 사용할 수 있다.

 

 

Global Queue는 qos(Quality-of-Service)를 이용하여 실행할 서비스의 중요도에 따라 우선 순위를 지정할 수 있다.

 

Global Queue가 선언된 형태를 보자.

 

Declaration

 

qos는 DispatchQoS의 QoSClass를 파라미터로 받는데 enum 타입이며 6가지가 있다.

 

userInteractive - 가장 높은 우선 순위. 사용자와의 상호 작용 또는 적극적인 업데이트(ex. 애니메이션) 을 위한 작업.

userInitiated - 두번째로 높은 우선 순위. 사용자에게 즉각적으로 반응해야하는 작업.

default - 기본 우선 순위. 앱이 초기화하거나 사용자의 행동에 대해 대신하는 작업.

utility - 낮은 우선 순위. 사용자가 앱을 사용하는 데에 방해되지 않는 작업.

background - 가장 낮은 우선 순위. 백그라운드에서 돌아가 사용자가 알지 못하는 작업.

unspecified - qos 클래스가 없음.

 

XCode상에서DispatchQueue를 타고 들어가면 아래와 같은 내용이 있다.

 

DispatchQueue 796Line

 

하단의 내용을 보면 우선 순위를 HIGH, DEFAULT, LOW로 나눠 userInitiated, default, utility로 매핑한다고 한다.

 

 

그런데 여기서 userInteractive가 가장 높은 우선 순위이면

 

Main Queue와 userInteractive의 우선 순위가 같은가? 하는 궁금증이 생겨서 찾아보니

 

여기에 비슷한 질문을 한 것이 있는데 답변들을 정리해보면 이렇다.

 

1) Main Queue는 userInteractive하지만 모든 global(qos: .userInteractive)가 Main Queue인 것은 아니다.

2) global(qos: .userInteractive)는 Main Thread에서 작동될 수 있다.

3) global(qos: .userInteractive)는 아주 빠르게 수행되어야하는데 이는 Main Queue로도 처리할 수 있기 때문에

거의 사용되지 않는다.

 

DISPATCH_QUEUE_PRIORITY_HIGH와 매핑되는 것이 userInteractive가 아닌, userInitiated인 이유도 이런 이유 때문인 것 같다.

 

userInitiated는 사용자가 직접 이 작업을 요청했고 대기 중이라는 것을 인지할만한 상황에 사용한다고 한다.

 

예를 들면 Pull-to-Refresh 기능 같이, 작업을 빨리 끝내야하지만 딜레이가 있을 수 있는 상황이다. (참고)

 

 

우선 순위를 default로 주고 async, sync 테스트 해봤다.

 

Global Queue - async, sync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var defaultQueue = DispatchQueue.global(qos: .default)
print("1")
defaultQueue.async {
    print("🔵A")
}
print("2")
defaultQueue.sync {
    print("🔵B")
}
print("3")
defaultQueue.async {
    print("🔵C")
}
print("4")
defaultQueue.sync {
    print("🔵D")
}
cs

 

결과

[실행 순서]

print("1") 실행

print("🔵A")Global Queue에 담고 비동기로 실행.

print("2") 실행

print("🔵B") Global Queue에 담고 동기로 실행.

print("3") 실행

print("🔵C") Global Queue에 담고 비동기로 실행.

print("4") 실행

print("🔵D") Global Queue에 담고 동기로 실행.

비동기로 실행한 A, C는 언제 실행이 될 지 예측할 수 없지만

A는 1 이후, C는 3 이후에 실행된다.

B, D는 동기로 실행했기 때문에 2 이후, 4 이후에 바로 실행된다.

 

 

Main Queue, Global Queue - async, 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
37
var defaultQueue = DispatchQueue.global(qos: .default)
DispatchQueue.main.async {
    print("🟡ㄱ")
}
defaultQueue.async {
    print("🔵A")
}
DispatchQueue.main.async {
    print("🟡ㄴ")
}
defaultQueue.sync {
    print("🔵B")
}
DispatchQueue.main.async {
    print("🟡ㄷ")
}
defaultQueue.async {
    print("🔵C")
}
DispatchQueue.main.async {
    print("🟡ㄹ")
}
defaultQueue.sync {
    print("🔵D")
}
DispatchQueue.main.async {
    print("🟡ㅁ")
}
defaultQueue.async {
    print("🔵E")
}
DispatchQueue.main.async {
    print("🟡ㅂ")
}
defaultQueue.async {
    print("🔵F")
}
cs

 

결과

[실행 순서]

print("🟡ㄱ") Main Queue에 담는다.

print("🔵A") Global Queue에 담고 비동기로 실행.

print("🟡ㄴ") Main Queue에 담는다.

print("🔵B") Global Queue에 담고 동기로 실행.

print("🟡ㄷ") Main Queue에 담는다.

print("🔵C") Global Queue에 담고 비동기로 실행.

print("🟡ㄹ") Main Queue에 담는다.

print("🔵D") Global Queue에 담고 동기로 실행.

print("🟡ㅁ") Main Queue에 담는다.

print("🔵E") Global Queue에 담고 비동기로 실행.

print("🟡ㅂ") Main Queue에 담는다.

print("🔵F") Global Queue에 담고 비동기로 실행.

Main Queue에 들어가있는 task 순차적으로 실행.

 

동기로 실행한 B, D는 순서대로 찍히고

A, C, E, F는 비동기로 실행했기 때문에 무작위로 찍힌다.

Global Queue는 Concurrent Queue이기 때문에 바로 실행되지만

Main Queue는 runloop가 다 끝난 후에 실행된다.

 

 

 

정리

DispatchQueue

Serial(순차적) / Concurrent(동시적)으로 나뉘며

각각 sync/async로 실행

 

Main Queue

Main Thread에서 돌아가는 Serial Queue

sync로 실행하면 데드락 발생하므로 금지

 

Global Queue

qos에 따라서 우선순위 부여할 수 있는 Concurrent Queue

 

Custom Queue

default는 Serial이며 attributes를 이용하여 Concurrent로 변경 가능

 

 

 

참고

https://zeddios.tistory.com/521

반응형