본문 바로가기

MOBILE/ios

[iOS] GCD의 기본: DispatchQueue의 종류와 특성

지난 번에는 GCD가 무엇인지 기본을 알아보았다.

 

[iOS] GCD의 기본: sync, async, serial, concurrent

ios multi-threading ios에서 멀티 스레딩을 지원하는 방식은 Thread, OperationQueue, GCD 이다. 이 중에서 Thread는 복잡하고 critical section을 막기 위한 lock, semaphore 등의 동기화 기법을 개발자가 직접..

josushell.tistory.com

이번에는 ios에서 제공하는 queue의 종류와 각각의 특성에 대해 알아보자.

위의 포스팅을 읽고 오는 것은 많은 도움이 된다.


DispatchQueue

앱의 main thread 또는 background thread에서 serial, concurrent 하게 task를 실행하도록 관리하는 객체

 

apple developer documentation

 

Dispatch queue는 task를 block 객체로 전달 가능한 FIFO queue이다. Dispatch queue는 task를 serial하게(한 thread에 작업을 몰아서 분배), 또는 concurrent하게(여러 thread에 작업을 나눠서 분배) 실행한다. dispatch queue로 전달된 작업은 시스템에 의해 관리되는 thread pool에서 실행된다. 앱의 main thread에서 실행되는 dispatch queue를 제외하고, 시스템은 어떤 thread가 task를 실행할지 보장하지 않는다.

 

scheduler는 작업을 동기적으로 또는 비동기적으로 실행한다. 만약 동기적으로 작업을 관리한다면, 실행이 종료되기를 기다린다. 비동기적으로 작업을 관리한다면, 작업의 실행과 상관없이 코드는 실행을 계속 진행한다.

 

주의!

main queue에서 작업을 동기적으로(synchronous) 실행하는 것은 deadlock을 발생시킨다.

 

Avoiding Excessive Thread Creation

concurrent하게 작업을 설계한다면, 현재 스레드의 실행을 막는 method를 불러서는 안된다.

concurrent dispatch queue에 의해 관리되는 task가 thread를 막을 경우에, 시스템은 다른 concurrent task를 실행하기 위해서 새로운 queue를 생성한다. 만약에 너무 많은 task가 멈춘다면 시스템이 사용 가능한 thread가 부족할 수 있다.

 

앱이 너무 많은 thread를 사용하는 또 다른 경우는, 바로 private concurrent dispatch queue를 많이 생성하는 것이다. 왜냐하면 각각의 dispatch queue는 thread 자원을 소비하고 추가 concurrent dispatch queue를 생성하는 것은 thread 낭비 문제를 더 심화하기 때문이다. private concurrent dispatch queue를 생성하는 대신에, global concurrent dispatch queue 중 하나에 task를 넘겨줘야 한다. serial task에 한해서는, global concurrent queue 중 하나에 serial queue를 전달하도록 설정해야 한다. 그 경우, thread를 생성하는 다른 queue의 수를 줄임으로써 queue의 동작을 유지할 수 있다.


 

ios에서 제공하는 DispatchQueue는 concurrency programming에 사용된다.

크게 총 3가지의 종류가 있다.

 

1. Main queue

2. Global queue

3. Custom queue

 

1. main queue : serial
DispatchQueue.main.async {
  //task
}

2. global queue : concurrent
DispatchQueue.global().sync {
  //task
}

3. custom queue : 사용자 지정 (serial or concurrent)
let queue = DispatchQueue(label: "queuename")

 

그렇다면 이들은 어떤 차이가 있을까?

 


Main Queue

 

이 큐에 보내진 task들은 모두 main thread에서 실행된다.

main thread는 프로세스에서 단 한개만 존재한다. 따라서 모두 main thread에서 실행되어야 하기 때문에 main queue는 단 한개만 존재하며, 인스턴스를 생성할 수 없고 프로퍼티 참조를 통해서 사용해야 한다.

또한 task들이 모두 main thread에서 처리되기 때문에 main queue는 serial queue이다. 

(참고로 UI 처리는 모두 main thread에서 처리된다.)

 

DispatchQueue.main.async {//task}
// 등등...

 

이때 주의할 점이 있다. 다음 코드는 굉장히 그럴듯해 보이지만 실제로는 deadlock이 발생한다는 점이다.

main queue는 sync로 구현해서는 안된다.

왜인지는 다음 포스팅에

 

DispatchQueue.main.sync {//task}

Global Queue

 

 

main queue와 달리 concurrent queue이다.

우선순위 (qos)에 따라 종류는 6개이다.

qos를 설정하지 않는 경우에는 default가 자동으로 설정된다.

 

DispatchQueue.global().sync {//task}
DispatchQueue.global().async {//task}

 

 

QOS

Quality Of Service는 작업의 중요도이다. 이 qos에 따라 ios는 자원의 분배를 차등으로 분배하며, 실행 순서도 달라진다.

 

 

userInteractive

animation, 이벤트 처리, UI 업데이트 등과 같이 사용자와 상호작용 하는 작업. 가장 높은 우선순위를 가진다.

사용자의 행동에 대해 바로 처리를 해야 하지만, 메인 스레드에서 로드가 오래 걸린다면 이를 userInteractive에서 처리해서 바로 동작하도록 보이게 한다.

 

userInitiated

email 로드와 같이 즉각적인 결과가 필요한 작업.

하지만 user-initiated tasks are second only to user-interactive tasks in their priority on the system 이다.

즉 userInteractive 보다는 우선순위가 낮다. 또한 사용자가 app을 사용하는 것을 막는다. (그 정도로 즉각적인 결과)

 

default

일반적인 작업.

 

utility

길게 수행되는 작업. 사용자에게 즉각적인 결과를 주지 않아도 되는 작업이다.

또한 사용자가 계속 app을 사용하는 것을 막지 않는다.

 

background

유저가 인지하지 않는 작업. 예를 들면, 백업과 같은 것들이다.

가장 낮은 우선순위를 가진다. 이 qos는 앱이 background 상태일 때 실행될 작업을 정의할 수 있다.

 

unspecified

없음!

 

우선순위가 높은 작업에 더 많은 스레드를 분배한다.

또한 작업이 아니라 queue에도 우선순위를 부여할 수 있다.

이 둘은 다른 결과를 가진다.

 

DispatchQueue.global(qos: default).sync // task qos
DispatchQueue.global().sync(qos: default) // queue qos

Custom Queue

사용자가 직접 생성가능한 queue이다.

기본은 serial 이지만, concurrent로 설정 가능하며

qos도 설정 가능하다.

 

let queue = DispatchQueue(label: "josushell") // josushell 이라는 큐 생성

let queue = DispatchQueue(label: "josushell", qos: .userInteractive) // qos 설정

let queue = DispatchQueue(label: "josushell", qos: .userInteractive, attribute: .concurrent) // concurrent 설정