본문 바로가기

iOS/학습정리

iOS) Dispatch (GCD)

Dispatch [Framework]

멀티코어 하드웨어에서 코드를 동시에(concurrently) 실행시키도록 도와주는 프레임워크

Grand Central Dispatch(GCD)로 알려졌으며, 시스템 수준에서 관리되는 dispatch queues를 이용한다.

하나의 애플리케이션이 멀티코어를 효과적으로 사용하기는 어렵다.
GCD는 시스템 레벨에서 작동하므로 모든 작동중인 애플리케이션에 대해 이용 가능한 시스템 자원을 적절하게 연결시켜줄 수 있다.

DispatchQueue [Class]

작업을 실행할 때 맵의 메인 스레드나 백그라운드 스레드에서 순차적(serial) 또는 동시적(concurrent)으로 실행하도록 관리하는 객체

DispatchQueue는 FIFO Queue이다. 작업은 block object 형태로 큐에 전달된다. DispatchQueue는 연속적 또는 동시적으로 작업을 실행하며, queue에 있는 작업은 시스템에 의해 관리되는 threads pod에서 실행된다. 앱의 main thread를 대표하는 큐를 제외하고는, 어떤 스레드가 어떤 작업을 할지 보장되지 않는다.

work item을 동기(sync) / 비동기(async)적으로 스케쥴링 할 수 있다 동기적으로 스케쥴링 할 경우 해당 work가 실행을 끝낼 때 까지 현재 스레드가 block되고 비동기적으로 스케쥴링할 경우 해당 work item이 다른 곳에서 실행중인 동시에 현 스레드가 block되지 않고 계속해서 실행할 수 있다.

DispatchQueue.main.sync와 Deadlock

Cocoatouch app에서는 기본적으로 main thread에서 동작하므로 DispatchQueue.main.sync를 하면 Deadlock에 빠진다.

과도한 스레드 생성 방지


concurrent하게 실행되도록 설정된 dispatch queue에서 현재 스레드를 block 하도록 하지 말자!
concurrent dispatch queue가 스레드를 block하면, 시스템은 queue에 있는 다른 concurrent task를 실행시키기 위해 추가적인 스레드를 생성한다. 따라서 너무 많은 task가 block 시키면 더이상 사용할 스레드가 바닥날지 모른다.

private concurrent dispatch queue을 너무 많이 생성해도 스레드를 과도하게 소비하게 될 수 있다!
각각의 dispatch queue가 스레드 자원을 소비하므로 추가적인 concurrent queue를 생성하는 것은 스레드 소비 문제를 악화시킨다. private concurrent queue를 생성하는 대신, global concurrent queue를 사용하는 것이 좋다.

어차피 concurrent queue이면 동시에 실행되고 누가 먼저 끝날지 보장되지 않으므로 굳이 새로운 private queue를 만들어서 thread를 낭비시킬 이유가 없다. global concurrent queue에 실행하는 것과 별 차이 없을 것이다. 

serial task의 경우 serial queue의 target queue를 global concurrent queue 중 하나로 지정한다. setTarget() 참고 이렇게 하면 스레드를 생성하는 queue의 개수를 줄이면서 serialized하게 동작하도록 보장할 수 있다.

main [Type Property]

현재 프로세스의 메인 스레드와 관련된 dispatch queue

시스템은 자동으로 메인 큐를 생성하고 앱의 메인 스레드와 연결시킨다.

글로벌 concurrent 큐와 마찬가지로 suspend(), resume(), dispatch_set_context() 등의 메소드는 메인 큐에서 사용되었을 때 아무런 효과가 없다.

메인 스레드는 너무 오래 응답이 없을 경우 에러가 발생하므로 오래 걸리는 작업은 global system queue나 다른 background dispatch queue에서 실행하도록 한다.

global(qos: ) [Type Method]

구체적인 quality-of-service class를 가진 global system queue를 반환하는 메소드

suspend(), resume(), dispatch_set_context() 등의 function은 메인 큐에서 사용되었을 때 아무런 효과가 없다.

DispatchQoS.QoSClass [Enumeration]

task 실행의 우선순위를 지정하는 Quality-of-Service class

case userInteractive
animations, event handling, updating user interface

case userInitiated
작업(task)에 대한 즉각적인 결과를 제공하거나 사용자가 앱을 사용하지 못하게 하는 작업에 할당.
예를 들어, 사용자에게 표시할 이메일의 내용을 로드하는데 사용한다.

case `default`

case utility
사용자가 계속해서 앱을 사용하는데 방해되지 않을 task에 부여.
예를 들어, 사용자가 진행 상황을 실시간으로 알 필요 없는 장기 실행 작업에 사용한다.

case background
앱이 백그라운드에서 돌아가는 동안 수행할 작업이나 dispatch queue에 부여한다.

case unspecified
qos class의 부재를 의미한다.

DispatchQueue.Attribute [Structure]

dispatch queue의 동작을 정의하는 속성

concurrent [Type Property]

이 속성이 없으면 큐는 serial하게 동작한다.

static let concurrent: DispatchQueue.Attributes

initiallyInactive [Type Property]

새로 생성된 큐가 inactive하도록 하는 것이다.

보통 새로 생성된 큐는 제공된 block(실행시킬 코드 블록)을 즉시 실행하도록 계획한다. 이 속성은 active() 메소드를 호출하기 전까지 큐가 block을 스케쥴링하지 않도록 한다.

static let initiallyInactive: DispatchQueue.Attributes

기타 관련 클래스

DospatchWorkItem [Class]

디스패치 큐나 디스패치 그룹에서 실행할 수 있도록 작업을 캡슐화 한다. completion handler나 excution dependency를 줄 수 있다.

DispatchGroup [Class]

하나의 단위로 모니터링하는 작업 그룹

Group은 일련의 작업을 통합하고 동작을 동기화 할 수 있다. 한 그룹 내에 있는 여러 작업에 대해 같거나 다른 큐에서 비동기적으로 실행되도록 스케쥴링 할 수 있다. 모든 work item의 실행이 끝나면, 그룹의 completion handler가 실행되는 방식이다. 물론, 그룹 내에 있는 모든 작업이 끝날때까지 동기적으로 기다릴 수도 있다.

enter() 메소드를 이용하여 명시적으로 그룹에 들어가는 것을 알리고 leave() 메소드로 그룹 내에 있는 block의 실행이 끝났다는 것을 알린다.

wait() 은 동기적으로 그룹 내의 모든 작업이 끝나길 기다리는 것이고, notify() 는 비동기적으로 기다리는 것이다. notify()를 사용할 때에는 끝난 후 실행할 작업에 대해 실행될 queue를 지정해야 한다.