Swift - GCD(Grand Central Dispatch) 기초

GCD(Grand Centeral Dispatch)라는 표현을 정확히 뭐라고 표현해야 할지는 모르겠다. 대충 클로져(Objective-C 에서는 블럭) 단위로 병렬 프로그래밍(Concurrency Programming) 혹은 멀티 스레딩(Multi Threading)을 하기 위한 기술로 생각하고 있다. 물론 이 기술은 기존 Objective-C 에서도 잘 사용하던 기술인데 C API로 구성되어 있어서 스위프트에서도 비슷하게 이용이 가능하다.

물론 그 중심에는 Dispatch Queue 라는 개념이 있다. 큐 기반 병렬 프로그래밍은 잘 알려진 기법 중 하나이기도 하다.

Update: 예제 코드를 Swift 3 기반의 문법으로 업데이트 하였다.

우선 간단한 예제 하나를 보자. 특정 연산 작업이 오래 걸려서 UI에 반응이 늦게 나타난다고 가정하고 이 연산 작업을 비동기(백그라운드)로 돌아가게 만들어보자.
let queue = DispatchQueue.global()
queue.async {
    var result = 0
    for i in 1...100 {
        result += i
    }
            
    println("Result = \(result)")
}
위 예제는 GCD를 이용해 비동기로(즉 백그라운드로) 1부터 100까지 값을 더한 뒤 콘솔에 출력하는 코드이다. 실제로 잘 동작하는 코드다. 물론 1에서 100까지 더하는 작업이 오래 걸리지는 않지만 -_- 예제니 그러려니 하자.

DispatchQueue 의 async 메소드(구 dispatch_async() 함수) 는 GCD를 이용해 특정 큐에다 클로져를 넘겨주기 위한 함수다. 마치 NSOperationQueue와 비슷하다. 하지만 별도로 NSOperation을 서브클래싱 할 필요도 없고 클로져로 간단하게 구현 할 수 있다.

DispatchQueue.global() 메소드 (구 dispatch_get_global_queue() 함수) 는 글로벌 큐(Global Queue) 라는걸 가져오기 위한 용도이다. 글로벌큐는 백그라운드로 돌아가는 스레드로 UI와는 별도로 동작한다.

보다싶이 실제 사용방법은 간단하다. 특히 Swift 3 부터는 함수가 아닌 클래스로 합쳐지면서 더욱 보기 좋은 형태로 변하였다.

Global Queue and Main Queue

큐는 여러 종류가 있다. 그 중에 필수적으로 알아둬야 할 큐는 글로벌 큐(Global Queue)와 메인 큐(Main Queue) 이다.

앞서 글로벌큐는 UI와는 별개로 동작하는 큐라고 이야기했다. 글로벌큐는 큐 중에서도 특이점이 있는데, 글로벌 큐는 작업이 들어오면 즉시 스레드를 생성해서 처리한다. 들어오는 만큼 작업을 무한대로 멀티스레딩 한다는 의미이다. 하지만 다른 큐들은 한 번에 하나씩만 작업을 처리한다.

메인큐는 반대로 UI가 돌아가는 큐라고 생각할 수 있다. 정답이다. 애초에 이 둘은 별개의 스레드에서 움직인다고 생각하면 된다.

그런데 둘이 별개 스레드라는 것은 둘 사이에 커뮤니케이션이 복잡해 질 수도 있다는 문제를 발생시킨다. 한 스레드에서 다른 스레드를 간섭하는 것은 thread-safe가 되어있는 클래스에서 lock을 유발하거나 thread-safe가 되어있지 않은 클래스에 동기화 문제를 일으킨다.

GCD는 이런 경우를 위해 큐 전환을 쉽게 하도록 설계되어 있다. 방법은 간단하다. 앞서 나왔던 예제를 고쳐보자.
DispatchQueue.global().async { 
    var result = 0
    for i in 1...100 {
        result += i
    }

    DispatchQueue.main.async {
        println("Result = \(result)")
    }
}
앞서 본 예제에서 콘솔에 출력하는 println 코드 주변이 async 로 또 감싸져 있다. 이번에도 GCD를 이용해 특정 큐에다 특정 작업을 몰아넣겠다고 하는 동일한 내용이다.

다만 이번에는 작업 큐를 main 이라는 녀석으로 가져왔다. 이 main 은 메인큐로써 즉 안에 쌓인 async 는 메인큐에서 작업을 돌리겠다는 의미다. 즉 클로져의 내용을 메인(UI)스레드에서 동작시킨다는 의미다.

이렇게 메인큐로 전환을 해서 UI를 엑세스 하는 코드를 넣으면 별개 스레드로 인한 문제를 미연에 방지 할 수 있다.

기본적인 GCD의 활용은 이 정도만 알아도 할 수 있다. 물론 천문학적인 연산을 마구 해대는 시스템이 아닐테니깐.

[돌아가기] Swift - 병렬 프로그래밍(Concurrency Programming)
[관련글] Swift - Dispatch Queue
[관련글] Swift - Dispatch Group
[관련글] Swift - 클로져(Closure)
[관련글] 스위프트(Swift) 가이드
[관련글] ​[iOS] 특정 코드를 비동기로 실행시키기 - Objective-C

댓글

익명님의 메시지…
좋은 정보 잘 봤습니다. ^^;

좋은 하루 되세요.

@AquaMacker
Unknown님의 메시지…
질문이 있습니다! 그럼 이 기능을 이용해 NStimer의 정확도를 높일 수 있나요...?

취미로 iOS용 메트로놈을 만드는 음악하는 학생인데요

단순히 NStimer만을 이용하기엔 정확도가 떨어지네요... 귀로들어도 알정도ㅠㅠ

그래서 구글링을하다하다 흘러흘러 여기까지 와서 질문을 남겨봅니다..

crakim7@naver.com
Seorenn님의 메시지…
김지환: GCD 는 병렬 프로그래밍을 위한 개념이라 타이밍과는 관계가 없습니다.

시간의 정확한 타이밍을 재고 싶다면 mach_absolute_time() 같은 무식한 함수나 CACurrentMediaTime() 같은 나노세건드 단위의 타이머까지 파고들 수는 있습니다. 우선순위가 높은 백그라운드 스레드를 하나 파서 최소시간 단위로 직접 스케쥴러 구현하시는 편이 나을수도 있겠네요.
Unknown님의 메시지…
와...친절한 답변 정말 감사합니다!! 좋은 하루 되세용~

이 블로그의 인기 게시물

소수점 제거 함수 삼총사 ceil(), floor(), round()

버전(Version)을 제대로 이해하기