その1では、man ページしかドキュメントのない状態で手探りで GCD をさわってみる、ということをしていました。
で、その1を書いた数時間後に全世界で Snow Leopard の発売開始が完了したためか、 ADC で Snow Leopard の開発資料すべてに、ヒラ会員でもアクセスできるようになっていました。すばらしい。
でもそのあたりはロクに読まないまま、今回も man ページを頼りに遊んでみます。
dispatch_apply
man ページによれば、データ並列を実現するもののようですね。与えられた Block を、指定された回数だけ並列実行して回す、という並列プログラミングパターン。
たとえばこんな感じになる。
実行結果:
% ./apply_dispatch 1 0 3 4 5 6 2 8 9 7
ちゃんと非決定的に、突っ込まれた順序でなく Block が実行されている様子が見てとれます。
とはいえ、 dispatch_apply はそのままだと、ループが終わるまで処理をブロックしてしまうので、それがイヤなら dispatch_apply そのものを dispatch_async でくるむといいよ、とか書いてます。
(さいしょ、iteration の各 Block が並列に実行されないのかと勘違いしていて、なんだよそれじゃ意味ないじゃんと思って実験してみたら、ちゃんと個々の Block が並列に実行されてるのを見て感心してました)
こんな感じになる。
実行結果は前のと同じ傾向になります。 違いは、 dispatch_apply の終了を待たずに main が終わりそうになるというところ。 (なので sleep で適当に待っている)
ローカル変数はどうなるの
Block の中から、外のスコープにある変数を参照するとどうなるでしょうか。
タイミング的には、
- x=3 in 外のスコープ
- Block を評価
- x=7 in 外のスコープ
- Block が実行される
となるように仕組んでいるわけです。
実行してみましょう。
% ./dispatch_local_var x in outside block : 7 x in async block : 3
おおっと、 Block から参照する、外のスコープにある変数は、 Block が評価された時点の値になるようですね。
クロージャ
それってなんてクロージャ、という。
Apple が GC と Block をC1X に提案している中で (APPLE’S EXTENSIONS TO C March 10, 2009)、Block はクロージャを C に導入する、といった文脈で提案されています。コールバック関数を実装する際に、いちいち関数にコンテキストを引数で渡すのはめんどうだし、コンテキストはレキシカルスコープにある情報そのものでしょうよ、という。
並列プログラミングするときに問題になるのがレースコンディションといった共有変数への競合アクセスですが、クロージャを使うことで評価時に値を束縛してしまうので、 Block が実行されるときの変数の値が決定的になる、ということでしょうか。
並列プログラミングとクロージャの関係についてはもうちょっと考えてみないと。
まとめ
dispatch_apply は、parallel_for のようなデータ並列モデルを提供するものです。また、 Block は C でクロージャを記述できるようにする構文拡張でした。
次は、上でさらっと流した変数を Block 間で共有するあたりの話を掘り下げてみたいと思います。あとイベントとかもセマフォとかも。 Queue でタスク間の依存関係の制御をするあたりも。おわらない…
参考
- Snow LeopardにGrand Central Dispatchが登場したわけ – builder by ZDNet Japan : 相変わらずわかりやすい紹介記事
- http://clang.llvm.org/docs/BlockLanguageSpec.txt
- http://clang.llvm.org/docs/BlockImplementation.txt
- 公開されている ADC のドキュメント
いま MacBook Pro を買うと Snow Leopard が入ってて Grand Central Dispatch で遊べるし NVIDIA GPU が載ってるので OpenCL でバリバリと次世代並列プログラミングができてよいんじゃないでしょうか。
日本語