読者です 読者をやめる 読者になる 読者になる

酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

SwiftでGrand Central Dispatch(GCD)を使った並列処理が実行される順番を調べてみた

Grand Central Dispatch(GCD)を使った並列処理が実行される順番を調べました。

やりたいこととしては、別スレッド上で処理してすべての処理が完了したらUIスレッドに終わった通知を送りたい。つまり並列処理の待ち合わせ(並列プログラミングガイド的にいえば「スレッドの合流」)をしなければいけません。

使えるのは、dispatch_group_async関数dispatch_apply関数でしょうか。

dispatch_group_asyncを使ってスレッドの合流をする

GCDを使って非同期にするためにはdispatch_syncdispatch_async関数を使うのがポピュラーです。ただこれらは単一のブロックしか処理できません。複数の非同期処理を管理するためにはdispatch_group_xxx関数を使用します。

dispatch_group_create関数でdispatch_group_tを生成して、dispatch_group_async関数で非同期したい処理を実装して、すべての処理が完了するのをdispatch_group_wait関数で待って、すべての処理が完了したらdispatch_group_notify関数で通知するという流れです。

let group: dispatch_group_t = dispatch_group_create()

for i in 0..<2 {
  dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
                
    NSLog("proc start \(i)")

    // なんかの重い処理
                
    NSLog("proc end \(i)")
  }
}

dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
  NSLog("notify")
}

NSLog("before wait")

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

NSLog("after wait")

上記のコードの結果です。ログの出力にNSLogを使っているのはスレッドがロックされるので実行順がわかりやすいからですね。実際には「proc start 0」「proc start 1」あたりが同時に実行されます。

2015-06-13 13:17:04.944 piyo[3681:242285] before wait
2015-06-13 13:17:04.944 piyo[3681:242352] proc start 0
2015-06-13 13:17:04.944 piyo[3681:242353] proc start 1
2015-06-13 13:17:04.944 piyo[3681:242352] proc end 0
2015-06-13 13:17:05.046 piyo[3681:242353] proc end 1
2015-06-13 13:17:05.046 piyo[3681:242285] after wait
2015-06-13 13:17:05.046 piyo[3681:242353] notify

上記のログの場合242285がmainスレッドかな。dispatch_group_notify関数は最後に走ったスレッド上(上記のログの場合は242353)で実行されるみたい。

dispatch_applyを使ってスレッドの合流をする

dispatch_apply関数は0〜10までを高速に並列処理することができます。dispatch_applyは処理が終わるまでスレッドをロックしますので、すべての処理が完了するのを待ちます。

NSLog("before apply")

dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { (i) -> Void in

  NSLog("proc start \(i)")

  // なんかの重い処理

  NSLog("proc end \(i)")
}

NSLog("after apply")

上記のコードの結果です。

2015-06-14 12:56:43.050 piyo[7332:744402] before apply
2015-06-14 12:56:43.051 piyo[7332:744402] proc start 3
2015-06-14 12:56:43.051 piyo[7332:744461] proc start 2
2015-06-14 12:56:43.051 piyo[7332:744472] proc start 7
2015-06-14 12:56:43.051 piyo[7332:744473] proc start 6
2015-06-14 12:56:43.051 piyo[7332:744471] proc start 4
2015-06-14 12:56:43.051 piyo[7332:744460] proc start 0
2015-06-14 12:56:43.051 piyo[7332:744470] proc start 5
2015-06-14 12:56:43.051 piyo[7332:744459] proc start 1
2015-06-14 12:56:44.052 piyo[7332:744461] proc end 2
2015-06-14 12:56:44.052 piyo[7332:744473] proc end 6
2015-06-14 12:56:44.052 piyo[7332:744402] proc end 3
2015-06-14 12:56:44.052 piyo[7332:744472] proc end 7
2015-06-14 12:56:44.052 piyo[7332:744459] proc end 1
2015-06-14 12:56:44.052 piyo[7332:744470] proc end 5
2015-06-14 12:56:44.052 piyo[7332:744471] proc end 4
2015-06-14 12:56:44.052 piyo[7332:744460] proc end 0
2015-06-14 12:56:44.052 piyo[7332:744461] proc start 8
2015-06-14 12:56:44.052 piyo[7332:744473] proc start 9
2015-06-14 12:56:45.053 piyo[7332:744473] proc end 9
2015-06-14 12:56:45.053 piyo[7332:744461] proc end 8
2015-06-14 12:56:45.053 piyo[7332:744402] after apply

挙動がちょっと変わっているのですが、一気に10個の処理が走るわけではなくて8個の処理を終えてから、残りの2個の処理を実行しています。同時にいくつ処理をさせるか自体についてはプログラマブルではないのでデバイスに応じて最適な数の並列処理を実行してくれるようです。

dispatch_applyの挙動について調べたところによると、過去の時点ではdispatch_applyは同時に2個ずつしか処理してくれなかったと記事を書いている方がいました。

参照