2015년 8월 25일 화요일

Swift - Dispatch Group

연산하는데 시간이 오래 걸리는 경우를 가정해보자. 당연히 기다리는데 지루할 것이다. 그런데 이 연산 작업을 쪼게어서 병렬로 처리하는게 가능하다면 당연히 (남는 CPU 코어를 이용해) 병렬처리 하는 것이 더 빠른 결과를 얻기 위한 방법이다.

다만, 병렬로 계산하기 때문에 모든 병렬 연산 작업이 끝나야 최종 결과물 산출이 가능해진다. 이 모든 병렬 연산이 끝나는 시점을 어떻게 파악할 것인가?

이번 글은 이런 병렬 작업의 처리가 모두 완료되었을 시점을 파악하는 방법, 다르게 말하자면 GCD의 디스패치 그룹(Dispatch Group)에 관한 내용이다.

Update: Swift 3 기반 문법으로 업데이트 하였다.

Dispatch Group

디스패치 그룹은 아래와 같은 식으로 사용된다. 아래 예제는 두 작업을 병렬로 실행시키고 이 두 작업이 모두 끝날 때 까지 대기하기 위한 코드이다.
let group = DispatchGroup()

group.enter()
DispatchQueue.global().async {
    // 작업할 내용 A
    ...
    group.leave()
}

group.enter()
DispatchQueue.global().async {
    // 작업할 내용 B
    ...
    group.leave()
}

...

// 작업 A, B가 모두 끝나길 기다린다.
group.wait(timeout: .distantFuture)
Swift 3 들어서 DispatchGroup 으로 클래싱 되면서 약간의 변화가 있었다. 이전에는 group에도 async 라는게 있었는데 이제는 enter() 와 leave() 라는 특수한 컨텍스트 관리 기반으로 바뀌었다. 하여간 코드에서 '작업할 내용' 이라는 주석이 붙은 위치를 보면 대충 이해는 갈 것이다.

마지막의 group.wait()는 해당 작업이 모두 끝날 때 까지 대기한다. timeout 인자로 넘겨진 .distantFuture 는 DispatchTime 에 정의된 값으로 forever 를 대체하는 의미로 생각된다.

wait()는 동기 형태이기 때문에 저기서 실행이 멈추게 된다. 만약 이 대기 또한 비동기로 바꾸고 싶다면 group.notify()를 이용하면 된다.
group.notify(queue: DispatchQueue.global()) {
    // 이 클로져는 그룹 내의 모든 작업이 끝나면 호출된다.
}
이런 식으로 그룹의 처리 내용(즉 병렬 연산)을 비동기로 돌려버릴 수 있다.

DispatchQueue 를 이용한 다른 그룹 사용법

위 방식은 enter 와 leave 쌍을 맞춰야 하는 단점이 있다. 그런데 그룹이 클래스로 디자인이 개편되면서 DispatchQueue 의 async 쪽으로 들어온 기능이 있다. 이 기능을 사용하면 아래와 같이 예제를 바꿀 수 있다.
let group = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: group) {
  // 작업할 내용 A
  ...
}
queue.async(group: group) {
  // 작업할 내용 B
  ...
}
group.notify(queue: DispatchQueue.global()) {
  // 작업 A, B가 모두 끝난 경우 호출된다.
}
마지막에 wait 가 아닌 notify 를 썼는데 wait 를 써도 된다. 오해하지 말자. ;-) 뭐 하여간 이런 방식이 좀 더 간단하고 가독성이 좋은 코드 같다.

물론 어떤 방법을 쓸지는 개인의 판단에 달렸다.

[돌아가기] Swift - 병렬 프로그래밍(Concurrency Programming) 가이드
[관련글] Swift - Dispatch Queue
[관련글] Swift - GCD(Grand Central Dispatch) 기초
[관련글] 스위프트(Swift) 가이드

댓글 4개 :

Unknown :

안녕하세요. 좋은 포스팅 덕분에 큰 도움을 받고 있습니다.

궁금한게 있는데요.

제가 지금 게임을 하나 만들고 있는데.. 배경 이미지가 움직이면서, 몬스터가 계속 리스폰 되는 게임입니다.

문제는 이에 해당되는 func을 update함수에 넣어서 굴리니까 부하가 너무 심한것 같습니다.

GCD에 대한 개념을 제가 잘 알고 있는게 맞다면, 개별 큐로 만들어서 돌리면 개선될거 같은데, 다른 방법이 있는지요.

참고로 배경이미지는 4개정도의 png레이어가 각각 다른 속도로 움직입니다.

댓글을 주셔도 되고 메일을 보내주셔도 좋습니다.

조언을 부탁드립니다. 감사합니다.

q-sun@naver.com

Seorenn :

코드 구조에 따라서 내용이 달라질 것 같아서 섣불리 댓글을 못 달겠네요. :-)

퍼포먼스 문제라면 GCD나 멀티스레딩 보다는
어떤 툴을 어떻게 쓰고 있느냐에 달린 문제 같습니다.
예를 들어 UIView 애니메이션인지 OpenGL ES인지 Metal 인지 아니면 별도의 엔진인지...

멀티 UIView 기반을 쓰고 있다면 싱글뷰에 멀티 레이어(CALayer)쪽을 더 보는게 좋을 것 같습니다만 그 외의 영역은 저도 지식이 별로 없는터라 조언을 못 드리겠네요.

Unknown :

즐거운 새해를 보내고 계신지요.

오랜 삽질과 고민끝에 해당 문제는 드디어 해결을 했습니다.

말씀하신대로 각 요소들을 UIImageview로 개별 동작 시키니까 부하가 많이 줄어든게 보입니다.

감사합니다 ^^;;

새해 복 많이 받으시길 빌겠습니다.

익명 :

많은 도움이 되었어요 ! 잘 읽고 갑니다 :)