2014년 6월 18일 수요일

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

댓글 4개 :

익명 :

좋은 정보 잘 봤습니다. ^^;

좋은 하루 되세요.

@AquaMacker

김지환 :

질문이 있습니다! 그럼 이 기능을 이용해 NStimer의 정확도를 높일 수 있나요...?

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

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

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

crakim7@naver.com

Renn Seo :

김지환: GCD 는 병렬 프로그래밍을 위한 개념이라 타이밍과는 관계가 없습니다.

시간의 정확한 타이밍을 재고 싶다면 mach_absolute_time() 같은 무식한 함수나 CACurrentMediaTime() 같은 나노세건드 단위의 타이머까지 파고들 수는 있습니다. 우선순위가 높은 백그라운드 스레드를 하나 파서 최소시간 단위로 직접 스케쥴러 구현하시는 편이 나을수도 있겠네요.

김지환 :

와...친절한 답변 정말 감사합니다!! 좋은 하루 되세용~