본문 바로가기

MOBILE/ios

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

ios multi-threading

ios에서 멀티 스레딩을 지원하는 방식은 Thread, OperationQueue, GCD 이다.

이 중에서 Thread는 복잡하고 critical section을 막기 위한 lock, semaphore 등의 동기화 기법을 개발자가 직접 관리해야 한다는 어려움이 있다. 또한 OperationQueue는 무겁고 필요한 코드가 많다는 단점이 있다.

 

이렇게 멀티 스레딩의 어려움을 운영체제 레벨로 넘겨서 개발자는 그저 멀티 스레딩을 하고자 하는 "의지"만 보여주면 되는 방식이 있다.

그것이 바로 Grand Centeral Dispatch, GCD 이다.

 

 

GCD(Grand Central Dispatch)라고도 하는 Dispatch에는 macOS, iOS, watchOS 및 tvOS의 멀티코어 하드웨어에서 동시 코드 실행 지원에 대한 체계적이고 포괄적인 개선 사항을 제공하는 언어 기능, 런타임 라이브러리 및 시스템 개선 사항이 포함되어 있습니다.


 BSD 하위 시스템, Core Foundation 및 Cocoa API는 모두 이러한 개선 사항을 사용하여 시스템과 응용 프로그램이 더 빠르고 효율적이며 향상된 응답성을 제공하도록 확장되었습니다. 단일 응용 프로그램이 여러 개의 코어를 효과적으로 사용하는 것이 얼마나 어려운지 생각해 보십시오. 컴퓨팅 코어 수가 다른 여러 컴퓨터에서 또는 이러한 코어를 놓고 경쟁하는 여러 응용 프로그램이 있는 환경에서 수행하는 것은 고사합니다. 시스템 수준에서 작동하는 GCD는 실행 중인 모든 응용 프로그램의 요구 사항을 더 잘 수용하여 균형 잡힌 방식으로 사용 가능한 시스템 리소스와 일치시킵니다.

 

GCD는 내부적으로 queue로 구현된다. 즉 task들이 FIFO 형식으로 지나간다. 이때 들어온 task들은 스레드로 나가게 된다.

즉 개발자가 멀티 스레딩을 할 작업을 그저 GCD에게 보내서 "나 멀티 스레딩 하고 싶은데?" 라는 의지만 보여주면, GCD는

"ㅇㅋ 내가 다 알아서 할게" 라고 하며 조건에 따라 스레드에 분배까지 해주는 것이다.

그리고 이렇게 task가 쌓이는 queue가 바로 DispatchQueue 이다.


DispatchQueue의 구분

dispatchqueue는 종류에 따라 크게 4가지로 구분된다. 이때 이를 나누는 기준은 다음과 같다.

 

동기 or 비동기를 결정하는

sync vs async

 

어떻게 thread에 작업을 분배할 것인지 결정하는

serial vs concurrent

 

따라서 이들의 조합 2X2 로 총 4개가 생성된다. 각각이 가지는 특성을 알아보자.

 


sync vs async

셜록과 왓슨이 조별과제를 하고 있다고 하자.
그런데 셜록이 PPT를 만들다가 할일이 너무 많은 것 같아서 왓슨에게 넘겨버렸다.
왓슨은 열심히 PPT를 만들고 있다...
대신에 셜록은 발표 대본을 쓰기로 하는데... 과연 셜록은 언제 대본을 쓸 것인가?

 

위와 같은 상황이 생겼다고 하자. 그럼 셜록은 발표 대본을 언제 쓰는 것일까? 두가지 경우가 있다.

 

1. 왓슨이 쓴 PPT가 완성되면, 이 PPT를 기반으로 발표 대본을 쓴다.

따라서 홈즈는 왓슨이 만드는 PPT가 완성되기를 기다려야 한다. 이것이 바로 동기, 즉 sync이다.

즉 작업이 끝나는 것을 기다렸다가 끝나면 실행하는 방식이다. 

 

2. 왓슨의 PPT는 대본에 큰 영향을 주지 않는다. 따라서 왓슨이 PPT를 만들고 있을 때, 동시에 발표 대본을 쓴다.

따라서 홈즈와 왓슨은 동시에 발표 대본을 쓰고 PPT를 만들게 된다. 이것이 바로 비동기, async 이다.

작업이 끝나는 것을 기다리지 않고 다른 작업을 시작한다.

 

홈즈가 메인 스레드이고 왓슨이 서브 스레드라고 생각하자.

서브 스레드에 task를 보내고 이 task의 종료를 기다린다면 sync, 서브 스레드에 task를 보내두고 종료와 상관없이 메인 스레드는 다음 task를 진행하는 것이 바로 async이다. 다음과 같이 사용한다.

 

sync: 동기

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

 

async: 비동기

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

 


serial vs concurrent

 

이번에는 홈즈과 왓슨이 다른 사람들과 조별과제를 한다고 하자.
홈즈는 조장이기 때문에 팀원들에게 할일 5개를 분배해야 한다.
그렇다면 홈즈는 이 5개의 task 분배를 어떻게 할 수 있을까?


 

 

1. 5개를 모두 왓슨에게 준다.

왓슨은 5개를 모두 혼자서 처리해야 한다. 이것이 바로 serial 이다.

즉 서브 스레드에게 task를 몰아서 주는 것이다.

 

2. 5개를 5명에게 하나씩 분배한다.

이것이 바로 concurrent이다. 즉 모든 서브 스레드에게 task를 나누어서 분배하는 것이다.

 

위 상황에서는 홈즈가 GCD이고 왓슨과 팀원들이 서브 스레드이다.

이 처럼 하나의 서브스레드에 task를 몰아주는 방식이 serial, 서브 스레드에게 task를 나눠서 분배하는 방식이 concurrent이다.

 

serial

 

concurrent

근데 serial vs concurrent는 약간 의문이 든다. " 아니 서브 스레드 하나에 작업을 다 몰아줄거면 왜 굳이 서브 스레드에서 처리를 하는 것이지? 전혀 concurrency의 장점이 없잖아" 라고 생각할지도 모른다. 하지만 serial은 작업이 하나의 스레드에 몰아있기 때문에 작업의 순서가 예측 가능하다. 하지만 concurrent는 여러개의 서브 스레드가 이를 나누어서 처리하기 때문에 작업의 순서 작업을 분배한 순서와 달리 뒤바뀔 수 있다. 따라서 순서가 중요한 작업은 serial을 꼭 써야하는 것이다.

 

이러한 queue에 특성에 따라 ios에서는 3가지 종류로 구분된다.

- main queue

- global queue

- custom queue

 

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

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

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

따라서 이를 조합하면 총 4개의 조합이 생성된다.

 

sync + serial

메인 스레드는 서브 스레드에게 넘겨준 task가 끝날때까지 기다리며, task들은 하나의 서브 스레드에 몰아서 담겨진다.

 

sync + concurrent

메인 스레드는 서브 스레드에게 넘겨준 task가 끝날때까지 기다리며, task들은 여러 스레드에 분배되어 처리된다. 따라서 해당 작업이 모두 끝나지 않아도 어떤 서브 스레드 하나에서 작업이 끝나면 메인 스레드는 다시 작업을 재개한다.

 

async + serial

메인 스레드는 서브 스레드에게 넘겨준 task가 끝날때까지 기다리지 않고 따로 실행하며, task들은 하나의 서브 스레드에 몰아서 담겨진다.

 

async + concurrent

메인 스레드는 서브 스레드에게 넘겨준 task가 끝날때까지 기다리지 않고 따로 실행하며, task들은 여러 스레드에 분배되어 처리된다. 

 

 

다음번에는 queue의 종류와 사용법에 대해서 알아보자.

 

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

지난 번에는 GCD가 무엇인지 기본을 알아보았다. [iOS] GCD의 기본: sync, async, serial, concurrent ios multi-threading ios에서 멀티 스레딩을 지원하는 방식은 Thread, OperationQueue, GCD 이다. 이 중에서..

josushell.tistory.com