iOS GCD — Concurrency and Threading
본글은 원문 번역을 중심으로, 유투브 자료를 참고해 작성한 글입니다. 출처는 아래에서 확인할 수 있습니다.
GCD Concepts
GCD를 이해하기 위해서 concurrency 와 threading 에 관련된 개념들을 알아야 한다.
Thread
스레드(thread)는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.
main thread(UI 관련) 와 background thread와 관련된 일들을 처리하는 것은 어렵고 복잡한 일이지만 다행히도 Apple 은 Grand Central Dispatch(GCD) 와 NSOperatin Queue 를 제공한다. 이는 thread를 만들거나 관리해야하는 어려운 작업들을 맡아서 해준다. 우리는 그저 task들이 담긴 queue를 만들고 GCD에 던져버리면 GCD가 모든 스레드를 관리해준다.
Concurrency
Concurrency 의 사전적 의미는 동시성이다. 같은 시간내에 다수의 작업이 실행되는 것을 뜻한다.
프로세스나 어플리케이션은 한 개 혹은 그 이상의 스레드를 가진다. OS의 scheduler 는 이러한 스레드를 각각 관리하고 동시에 실행 가능하게 한다(멀티 스레딩).
싱글 코어 디바이스는 time-slicing 이라는 방법을 통해 동시성을 획득하고, 멀티코어 디바이스는 parallelism을 통해 동시성을 획득한다.

GCD는 스레드들의 가장 상위에 있는 존재로 공유되는 스레드 풀을 관리한다. GCD를 이용해서 작업들을 dispatch queues에 추가할 수 있고 GCD는 어떠한 스레드가 이것들을 실행할지 결정한다.
Queues
맛집에 사람들이 줄을 서있을 때, 첫번째 사람은 첫번째로 입장하고 두번째 사람은 두번째로 입장한다. 이렇게 First In First Out(FIFO) 구조를 가진 것이 Queue이다. 우리가 실행해야 할 작업(task)들을 줄 세우고, 큐에 넣으면 먼저 들어간 것은 먼저 실행된다.
GCD는 DispatchQueue 라는 클래스를 통해서 dispatch queue를 적절히 실행한다. 작업의 단위들을 이 큐에 넣으면 GCD가 FIFO 순서로 이것들을 수행한다.
Dispatch queues들을 thread-safe 한데 이는 multiple threads 로 부터 동시에 접근 가능함을 의미한다.
Queue에는 Serail 과 Concurrent 두가지 타입이 있다. 어쨌거나 둘 다 Queue 니까 먼저 들어온 것이 먼저 실행 된다. 차이점이 있다면 serial 은 첫번째 작업이 끝난 후에 두번째 작업이 실행되지만, concurrent 는 첫번째 작업을 시작한 후, 그 작업이 끝날때까지 기다리지 않고 바로 두번째 작업을 시작한다는 점이다.
Serial 큐는 오직 하나의 작업만이 주어진 시간에 실행됨을 보장한다. GCD가 이 수행 시간을 관장한다.

- 순서 예측 가능
- 늦게 실행 된 것이 먼저 끝나는 상황 (race condition) 방지
- 느림 (모든 작업들이 그 전 작업이 끝나길 기다렸다가 하나씩 실행 되기 때문)
Concurrent queue는 다수의 작업이 같은 시간에 실행되게 한다. 큐는 작업들이 순서대로 실행됨을 보장하지만 그것들이 끝나는 시간은 알 수 없다.

- 순서 예측 불가
- 빠름
예를 들어, 만약 사용자의 많은 정보를 저장해야한다고 할 때, 어떤 정보가 먼저 들어와야 할지 순서는 중요하지 않다. 그냥 빠르면 좋다. 이런 상황에서는 concurrent queue 를 쓴다. 하지만 반대로 순서가 중요하다고 할때는 serial queue를 쓴다
GCD 는 세가지 메인 타입 큐를 제공한다.
- Main queue: main thread 위에서 동작하며 serial queue이다.
let mainQueue = DispatchQueue.main2. Global queues: 모든 시스템에서 공유되는 concurrent queue이다. high, default, low, background 네가지의 다른 우선순위를 가질 수 있다. 이 우선순위를 직접적으로 지정하는 대신 QOS(Quality of Service) class의 프로퍼티를 지정한다. 이는 작업의 중요도를 나타내고 GCD가 작업의 우선순위를 결정하는데 가이드를 제시한다.
let backgroundQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)QoS classes 에는 다음과 같은 네가지 프로퍼티가 있다.
- User-interactive: 좋은 사용자 경험을 제공하기 위해 바로 완료되어야 하는 작업들이 들어간다. UI 업데이트나, 이벤트 처리나 지연이 적은 작업 등에 사용한다. 전체 앱에서 이 클래스에서 수행되는 총량은 적어야 하고 메인 스레드에서 수행되어야 한다.
- User-initiated: 사용자가 즉각적인 결과를 기다리고 있고 UI 상호 작용을 계속하는 데 필요한 작업에 사용한다. 높은 우선순위의 global queue에서 수행된다.
- Utility: progress indicator가 보이는 것과 같은 오래 걸리는 작업들이 들어간다. 계산, I/O, 네트워킹, 연속적인 데이터 피드 등 지속적인 작업이 필요한 경우에 사용한다. 낮은 우선순위의 global queue에서 수행된다.
- Background: 유저가 당장 신경쓰지 않는 작업들이 들어간다. prefetching, maintenance 과 같은 유저 인터렉션이 필요하지 않거나, 시간에 민감하지 않은 작업에 사용한다. background priority global queue에서 수행된다.
3. Custom queues: 직접 만드는 queue로 serial 일수도, concurrent 일 수도 있다.
//custom serail queue
let serialQueue = DispatchQueue(label: “com.example.serial”)//custom concurrent queue
let concurrentQueue = DispatchQueue(label: “com.example.concurrent”, attributes: .concurrent)
Synchronous vs. Asynchronous
GCD를 통해 synchronously 혹은 asynchronously 하게 작업을 실행시킬 수 있다.
synchronous function 은 작업들이 완료 된 후, caller 에게 컨트롤을 반환한다.
DispatchQueue.sync(execute:)
asynchronous function은 작업이 시작되는 실행 순서는 순차적이지만 그것이 완료될때까지 기다리지 않고 컨트롤을 즉각 반환한다. 따라서 asynchronous function 은 현재의 스레드가 다음 fuction 을 실행하는것을 막지 않는다. DispatchQueue.async(execute:)
//custom concurrent queuelet queue1 = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)//custom serail queuelet queue2 = DispatchQueue(label: "com.example.serial")queue1.async {printFiveTimes("🍏")}print("a0")queue1.sync {printFiveTimes("🌕")}print("a1")queue2.async {printFiveTimes("🍎")}print("a2")queue2.sync {printFiveTimes("💜")}print("a3")//prints🍏 0a0🌕 0🍏 1🌕 1🍏 2🌕 2🍏 3🌕 3🍏 4🌕 4a1🍎 0🍎 1🍎 2a2🍎 3🍎 4💜 0💜 1💜 2💜 3💜 4a3//설명
- queue1.async 🍏 => async로 실행되었기 때문에 🍏가 등록되자마자 control 이 반환되어 🌕 도 queue 에 등록되고 🍏가 미처 끝나기 전에 a0 출력. 이때, concurrent queue이기 때문에 🍏와 🌕이 print 되는 순서는 뒤섞임- queue1.sync 🌕 => sync로 실행되었기 때문에 🌕 출력이 끝난 후에야 a1 출력- queue2.async 🍎 => aysnc 로 실행되었기 때문에 🍎 가 등록되자마자 control 이 반환되어 💜도 queue 에 등록되고 🍎가 미처 끝나기 전에 a2 출력. 이때, serial queue이기 때문에 🍎의 출력이 끝나고 💜 출력- queue2.sync 💜 => sync 로 실행되었기 때문에 💜 출력이 끝난 후에야 a3 출력
출처
https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2
https://www.youtube.com/watch?v=iTcq6L-PaDQ&t=54s
https://ko.wikipedia.org/wiki/%EC%8A%A4%EB%A0%88%EB%93%9C_(%EC%BB%B4%ED%93%A8%ED%8C%85)
