DispatchQueue란, Swift에서 동시성 프로그래밍을 돕기 위해 만든 GCD(Grand Central Dispatch) 프레임워크 기술이다.
동시성 프로그래밍은 뭐고, GCD는 뭐고, 그래서 결론적으로 DispatchQueue는 뭘까 용어들을 차근차근 정리해보려 한다.
동시성 프로그래밍이란, 여러가지 일을 동시에 작업하도록 하는 프로그래밍을 말한다. 동시성 프로그래밍을 가능하게 하는 건 멀티 스레딩이다. 스레딩이 뭘까? 여러 개의 스레드를 통해 비동기적으로 작업을 수행하는 것이다. 스레드는 뭘까? 프로세스 내에서 작업을 수행하는 단위다.
실행 중인 프로그램, 실행하기 위해 메모리에 올라 온 프로그램을 프로세스라고 한다. iOS 앱도 프로세스라고 할 수 있다. 개발의 결과물인 앱은 iOS 운영체제에 속한 프로그램이다. 프로그램을 실행하면 프로세스라고 부르는 것이다. 프로세스 내부에는 여러 개의 스레드가 존재한다.
프로세스 내에서 작업을 수행하는 단위다. 한 개의 프로세스 내에서 여러 개의 스레드가 동시에 작업을 수행할 수 있다. 앱을 실행 시키면 여러 개의 스레드가 동시에 일을 하는 것이다.
크게 메인 스레드와 백그라운드 스레드로 구분한다. 여러 개의 스레드를 가지고 동시에 작업을 하는 것을 멀티 스레딩이라고 한다.
GCD(Grand Central Dispatch)는 동시성 프로그래밍을 돕는 프레임워크다. Swift에서 사용하는 GCD 기술로는 DispatchQueue가 있다. 멀티 스레딩 작업을 하며 비동기적으로 작업을 수행하기 위해서 GCD 기술을 사용한다. 구체적으로 어떤 작업을 어떤 스레드에서 수행할지 코드로 지정하는 것이다.
DispatchQueue라는 단어의 의미를 살펴보면, 큐(Queue)를 어디론가 보낸다(Dispatch)는 말이다. 자료구조인 큐 중에서도 우선순위 큐는 데이터가 우선순위대로 빠져나가는 구조다. 우선순위는 내부 규칙에 따라 지정한다. 디스패치 큐가 이런 구조인데, 개발자가 작업을 디스패치 큐에 보내면 운영체제가 내부 규칙에 따라 적절한 스레드에 작업을 할당시킨다. (과거 프로그래밍에서는 개발자가 직접 스레드를 생성한 뒤 작업을 할당시켰다고 한다.)
DispatchQueue에 작업을 넘기는 코드는 아래와 같은 형식이다.
DispatchQueue.{Queue종류}.{QoS옵션}.{sync/async} {
// 수행할 작업 코드 //
}
Queue종류 : Main, Global, Custom
QoS(Quality of Service) : 작업 중요도
sync: 동기적으로 작업 수행
async: 비동기적으로 작업 수행
메인 큐는 앱의 메인 스레드에서 실행되는 큐로서 우선순위가 높은 UI 업데이트 관련 작업을 처리한다. 메인 큐의 종류는 직렬 큐(Serial Queue)다. 직렬 큐는 들어온 작업들을 한가지 스레드에 모두 보내고 작업을 한번에 하나씩 순서대로 실행하는 큐다. 스레드 하나에 모든 작업을 할당하기 때문에 작업 완료 순서가 보장된다.
DispatchQueue.main.async {
// ui update code
}
메인 스레드에서는
sync사용 시 데드락 현상이 발생하기 때문에async만 사용해야 한다.
글로벌 큐는 백그라운드에서 실행되는 큐로서 사용자에게 당장 보일 필요가 없는 우선순위가 낮은 작업을 처리한다. 글로벌 큐의 종류는 동시성 큐(Concurrent Queue)다. 동시성 큐는 들어온 작업들을 여러 스레드에 나눠 보내고 동시에 병렬적으로 실행하는 큐다. 여러 스레드에서 동시에 작업하기 때문에 작업 완료 순서는 보장되지 않는다.
DispatchQueue.global().async {
// network 통신, 계산이 무거운 작업 등을 백그라운드에서 수행
// 결과를 ui에 반영할 때는 메인 스레드에서 수행
DispatchQueue.main.async {
// ui update code
}
}
커스텀 큐는 개발자가 label에 큐의 고유한 이름을 설정할 수 있다. 또, 작업을 직렬 뷰에 보낼지 동시성 큐에 보낼지도 attributes에 지정한다. 지정하지 않으면 기본적으로 serial(직렬)큐로 설정된다.
// 커스텀 큐 정의
let customQueue = DispatchQueue(label: "com.myapp.customqueue", attributes: .concurrent)
// 커스텀 큐 실행
customQueue.asyns {
// 커스텀 큐에서 실행할 작업 code
}
작업의 중요도를 시스템에 알려주는 값이다. 커스텀 큐를 생성할 때 우선순위를 부여하기 위해 사용한다. label과 attributes처럼 qos 파라미터를 통해 부여한다.
let customQueue = DispatchQueue(
label: "com.myapp.customqueue",
qos: .userInitiated,
attributes: .concurrent
)
qos를 통해 OS에 작업의 중요도를 알려서 OS가 작업을 할당하는데 도움을 준다. 이를 통해 시스템은 리소스(CPU 시간, 에너지 등)를 효율적으로 분배할 수 있다.
우선순위가 높은순으로 살펴본다.
.userInteractive : 최고 우선순위. 지금 당장 해야하는 작업. UI 업데이트, 애니메이션 등 매우 빠르게 처리되어야 하는 작업을 할당한다..userInitiated : 높은 우선순위. 사용자가 기다리고 있는 작업. 버튼을 탭해서 데이터 로딩이 되는 상황 등 몇 초 안에 완료되어야 하는 작업을 할당한다..default : 기본 우선순위. 평범한 작업, 일반적인 백그라운드 작업을 할당한다. 특별한 지정이 없을 때 사용된다..utility : 낮은 우선순위. 천천히 해도 되는 작업. 데이터 다운로드나 백업 등 시간이 걸리지만 즉시 필요하지 않는 작업을 할당한다..background : 최저 우선순위. 언제 끝나도 상관없는 작업. 대용량 데이터 정리, 동기화 등 사용자가 직접적으로 인지하지 못하는 작업을 할당한다..unspecified : 시스템에게 우선순위 결정을 맡기는 값. 시스테이 알아서 우선수위를 결정한다. 어떤 우선순위로 처리해도 상관없는 작업을 할당한다.Main Queue는 항상 .userInteractive로 처리된다.
우선 작업이 완료되는 데에 n초가 걸리는 n번 task를 함수로 정의했다.
func testTask(_ taskNumber: Int, _ timeInterval: TimeInterval) {
print("Task \(taskNumber) started")
Thread.sleep(forTimeInterval: timeInterval)
print("Task \(taskNumber) finished")
}
started를 출력한 n초 후에 finished를 출력하는 task
// 직렬 큐 정의
let serialQueue = DispatchQueue(label: "com.myapp.myqueue")
serialQueue.sync {
testTask(1, 2)
}
print("hello 1")
serialQueue.async { [weak self] in
self?.testTask(2, 2)
}
print("hello 2")
serialQueue.async { [weak self] in
self?.testTask(3, 2)
}
print("hello 3")
작업 수행에
2초가 걸리는 task3개를 선언했다. task 1은sync, task 2, 3은async다. 각 task 사이에 큐에 할당하지 않은 일반

sync에 의해Task 1이 완료된 뒤에서야 그 다음Task 2가 완료된 뒤Task 3이 실행되었다.Task 2와Task 3은async기 때문에 그 사이에 있는
// 동시성 큐 정의
let concurrentQueue = DispatchQueue(label: "com.myapp.myqueue", attributes: .concurrent)
concurrentQueue.sync {
testTask(1, 2)
}
print("hello 1")
concurrentQueue.async { [weak self] in
self?.testTask(2, 2)
}
print("hello 2")
concurrentQueue.async { [weak self] in
self?.testTask(3, 2)
}
print("hello 3")
직렬 큐와 같은 task를 그대로 선언하였다.

sync에 의해Task 1이 끝난 뒤에야Task 2와Task 3이 한꺼번에 실행되는 것을 확인할 수 있다.
// Task 1 : background
concurrentQueue.async(qos: .background) { [weak self] in
self?.testTask(1, 2)
}
// Task 2 : user interactive
concurrentQueue.async(qos: .userInteractive) { [weak self] in
self?.testTask(2, 2)
}
// Task 3 : utility
concurrentQueue.async(qos: .utility) { [weak self] in
self?.testTask(3, 2)
}
// Task 4 : user initiated
concurrentQueue.async(qos: .userInitiated) { [weak self] in
self?.testTask(4, 2)
}
Concurrent Queue에async로 각각 다른QoS를 지정하여 4개의 똑같은 task를 선언하였다.

User Interactive→User Initiated→Utility→Background순으로 출력된 것을 확인할 수 있다.