안녕하세요, iOS 개발하는 루피입니다.
오늘은 지난 글을 작성하면서 생겼던 궁금증을 해결해보는 시간을 가져보도록 하겠습니다.
바로 시작합니다.
궁금증 발생
Dispatch Queue에 대해 공부하면서 Concurrent + Sync 형태의 동작에 대해 의문을 가지게 되었습니다.
Button("concurrent - sync") {
print("A")
concurrentQueue.sync {
sleep(3)
print("B")
}
print("C")
}
문제의 그 코드입니다.
❓저의 생각은 이렇습니다. (처음 생각)❓
Concurrent는 단일 Thread가 아닌 여러 Thread에 작업을 할당하는 Queue의 종류입니다.
즉, 직렬의 방식이 아닌 병렬의 방식으로 작업을 분배합니다.
Sync는 등록한 작업의 응답을 기다리는 동작 방식입니다. 즉, 다른 작업을 먼저 진행하는 것을 방지합니다.
따라서, print("A")와 print("C")는 같은 Thread에서 실행되고, Print("B")는 다른Thread에서 실행되기에 A와 B는 서로다른 Thread에서 동시에 실행되고, C는 A 작업이 종료될때까지 기다리다가 종료되면 실행되고, B는 3초가 지난 후에 출력된다. 근데 출력문이 3초 이상 걸릴리가 없으니깐 "결국 전체 출력문은 ACB가 된다!!"라고 생각했습니다.
하지만, 출력문은 ABC로 출력 되었습니다.
왜 그럴까?? 생각 하던, 저는 단일 Thread에서 작업이 실행되고 있다는 의심을 하였으며, 이는 곧 Concurrent는 여러 Thread에 작업을 할당한다는 내용을 반박하는 내용이 되기에 제대로 이해하고 글을 작성하게 되었습니다.
1. 진짜 단일 Thread에서 실행되는가?
Button("concurrent - sync") {
print("A",Thread.current)
concurrentQueue.sync {
print("B",Thread.current)
}
print("K",Thread.current)
concurrentQueue.sync {
print("D",Thread.current)
}
print("C",Thread.current)
}
조금 더 정확한 테스트를 위해 몇 가지 출력문을 추가했습니다.
결과는...?
A <_NSMainThread: 0x600001704040>{number = 1, name = main}
B <_NSMainThread: 0x600001704040>{number = 1, name = main}
K <_NSMainThread: 0x600001704040>{number = 1, name = main}
D <_NSMainThread: 0x600001704040>{number = 1, name = main}
C <_NSMainThread: 0x600001704040>{number = 1, name = main}
저의 예상처럼 5가지 작업 모두 메인 Thread 하나에서 실행되고 있던 것을 확인할 수 있었습니다.
2. 그러면 내가 공부한 개념이 틀린건데...?
오늘도 하나 배웠습니다. 잘못 이해했습니다. 바로 잘못된 지식을 수정해보도록 하겠습니다.
Concurrnet Queue는 여러 Thread에서 작업을 처리가 가능 하도록 하는 것이지 무조건 여러 Thread에서 작업을 처리하는 방식이 아니었습니다.
따라서 sync를 사용할 경우 호출된 Thread에서 즉시 블로킹이 실행되며, 작업이 완료될 때까지 다음 코드를 진행하지 않고 기다리게 됩니다.
조금 더 쉽게 말하자면, Concurrent Queue는 여러 Thread에서 작업을 처리할 잠재력이 있지만, sync를 사용할 때는 그 잠재력이 무시되고, 호출 Thread(여기서는 메인 Thread가 되겠죠!!)에서 모든 작업이 순차적으로 실행된다는 겁니다!!
3. 이 참에 테스트한 내용을 정리해보자!
1 ) Concurrent + async
print("A",Thread.current)
concurrentQueue.async {
print("B",Thread.current)
}
print("K",Thread.current)
concurrentQueue.async {
print("D",Thread.current)
}
print("C",Thread.current)
결과
A <_NSMainThread: 0x600001700100>{number = 1, name = main}
K <_NSMainThread: 0x600001700100>{number = 1, name = main}
B <NSThread: 0x600001792b40>{number = 11, name = (null)}
C <_NSMainThread: 0x600001700100>{number = 1, name = main}
D <NSThread: 0x600001728140>{number = 8, name = (null)}
async 방식으로 동작하기에 순서를 보장하지 않습니다. 순서가 보장되는 것은 메인 Thread에서 처리하는 A 이후에 K 그 이후에 D가 출력되는 것뿐이며, 그 사이사이에 B가 출력될지 아니면 D가 먼저 출력될지는 알 수 없습니다.
2 ) Serial + async
print("A",Thread.current)
serialQueue.async {
print("B",Thread.current)
}
print("K",Thread.current)
serialQueue.async {
print("D",Thread.current)
}
print("C",Thread.current)
async 방식으로 동작하지만, 위 Concurrent 때와 다르게 한 번에 하나의 작업만 처리합니다.
따라서 A 출력 이후 B가 먼저 처리된다면 B가 먼저 처리될 수 도 있고 K가 먼저 처리될 수 있습니다.
여기서 보장되는 것은 Queue 성질로 인해 같은 Thread에 할당된 작업의 순서만 보장됩니다.
결과
A <_NSMainThread: 0x600001704040>{number = 1, name = main}
K <_NSMainThread: 0x600001704040>{number = 1, name = main}
B <NSThread: 0x600001706e80>{number = 10, name = (null)}
C <_NSMainThread: 0x600001704040>{number = 1, name = main}
D <NSThread: 0x600001706e80>{number = 10, name = (null)}
3 ) Serial + sync
print("A",Thread.current)
serialQueue.sync {
print("B",Thread.current)
}
print("K",Thread.current)
serialQueue.sync {
print("D",Thread.current)
}
print("C",Thread.current)
결과
A <_NSMainThread: 0x600001704040>{number = 1, name = main}
B <_NSMainThread: 0x600001704040>{number = 1, name = main}
K <_NSMainThread: 0x600001704040>{number = 1, name = main}
D <_NSMainThread: 0x600001704040>{number = 1, name = main}
C <_NSMainThread: 0x600001704040>{number = 1, name = main}
4 ) concurrent + sync
print("A",Thread.current)
concurrentQueue.sync {
print("B",Thread.current)
}
print("K",Thread.current)
concurrentQueue.sync {
print("D",Thread.current)
}
print("C",Thread.current)
결과
A <_NSMainThread: 0x600001704040>{number = 1, name = main}
B <_NSMainThread: 0x600001704040>{number = 1, name = main}
K <_NSMainThread: 0x600001704040>{number = 1, name = main}
D <_NSMainThread: 0x600001704040>{number = 1, name = main}
C <_NSMainThread: 0x600001704040>{number = 1, name = main}
두 가지 경우 모두 sync 처리하기 때문에 호출된Thread에서 blocking 발생합니다. 따라서 호출 Thread에서만 처리하는 것을 볼 수 있습니다.
출처
https://namitgupta.com/an-in-depth-look-at-grand-central-dispatch-gcd-in-swift
An in-depth look at Grand Central Dispatch (GCD) in Swift
Our blog post on Grand Central Dispatch (GCD) in Swift covers queue types, methods, and code samples to optimize task execution and enhance app performance.
namitgupta.com
https://www.swiftpal.io/articles/difference-between-concurrent-queue-and-serial-queue-in-swift
Difference between Concurrent Queue and Serial Queue (GCD - Grand Central Dispatch) in Swift - Swift Pal
Threading is essential to make use of our multi-core iPhone’s. Learn how to use GCD (Grand Central Dispatch), and know when to use Concurrent Queues and Serial Queues.
www.swiftpal.io
https://www.avanderlee.com/swift/concurrent-serial-dispatchqueue/
Concurrent vs Serial DispatchQueue: Concurrency in Swift explained
The differences between concurrent and serial dispatchqueue explained in Swift. How to use the main thread, background thread and prevent data races.
www.avanderlee.com
오늘도 화이팅입니다.
'iOS > Swift' 카테고리의 다른 글
[Swift] Dispatch Group (0) | 2025.02.23 |
---|---|
[Swift] Dispatch Queue 종류 (0) | 2025.02.21 |
[Swift] DispatchQueue (0) | 2025.02.19 |
[Swift] GCD를 공부해봅시다. (0) | 2025.02.18 |
[Swift]DispatchObject (0) | 2025.02.13 |