Grand Central Dispatch その2 Queue


Queueを作る

ディスパッチの基本的な使い方は、処理したいコードをブロックや関数で用意しておいて、キューにそのブロックや関数を渡すという流れになります。

ディスパッチのキューには2種類あります。アプリ(OS?)に最初から一つ用意されているグローバルキューと、自分で作るキューです。グローバルキューは並列に処理できますが、自分で作るキュー(以後シリアルキューと書いて区別します)は入れていった順番に直列でしか処理できません。

キューはdispatch_queue_tという型のオブジェクトで表されます。ディスパッチのオブジェクトは他にもいくつかありますが、それは次回以降に後回しにします。

グローバルキューを取得するにはこの関数を使います。

dispatch_queue_t
dispatch_get_global_queue(long priority, unsigned long flags);

一つ目の引数にはキューが処理される優先度を指定します。普通はDISPATCH_QUEUE_PRIORITY_DEFAULTを指定すれば良いと思います。DEFAULT以外にもHIGHとLOWがあります。HIGHの処理が全て終わってからDEFAULT、DEFAULTが全て終わってからLOWという順番で処理されていくようです。2つ目の引数はヘッダを見るとReserved for future useとなっていて今は何も意味しないので、0を入れておけば良いです。

シリアルキューはこの関数で作ります。

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

一つ目の引数はキューに付けられるラベルで、サンプルを見る限り逆DNS記法が推奨されているようです。でも、同じラベルでふたつキューを作っても、同じキューが返されるという事は無く、別のキューが作られるようです。2つ目の引数はこれまたヘッダにUnusedなんて書かれていて、こちらはNULLでも入れておけば良いようです。

なお、Carbonに慣れ親しんでいる方はお分かりかと思いますが、関数名にcreateとついているのでdispatch_queue_createで作ったキューは使い終わったら解放しなければいけません。それを行うのはdispatch_releaseです。

dispatch_release(queue);

使うかどうか分かりませんが、リテインカウントをあげる事も出来ます。

dispatch_retain(queue);

これらretainとreleaseはキュー以外のオブジェクトにも使います。

Queueに処理を渡す

キューを取得できたら、ブロックを渡して処理をさせます。キューを使って処理を開始させる関数はいろいろありますが、まず基本的な2つを見てみます。こんなかんじで定義されています。

void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

ayncは非同期でバラバラに、syncは同期で順番に、キューにブロックを処理をさせようとする関数です。と説明すると、キューにも同じような用途で2種類あるのに何で関数がまた同じように2種類分かれているんだと思ってしまうのですが、ちょっと役割が違います。

ブロックが処理されるスレッドを調べてみると、asyncは呼び出し元のスレッドとは別のスレッドでブロックを処理させ、syncは呼び出し元と同じスレッドで処理をさせるようです。

実際の挙動としては、グローバルキューにasyncでブロックを渡すと別スレッドで並列に実行されますが、syncで渡すと現在のスレッドをつかうのでそのまま直列に実行されます。シリアルキューにsyncで渡せばもちろんそのまま直列に実行ですが、asyncで渡すと入れていった順番に別スレッドで実行されます。

グローバルキュー+syncの組み合わせはありえないような気がするんですが、もしかしたら想像もつかない用途があるかもしれません。他にもディスパッチ用の関数はたくさんあるんで意味が無さそうな組み合わせはいろいろ出てくるかもしれません。

それと、ここでつかえるブロックは返り値も引数もなしのブロックです。

実行してみる

実際にディスパッチを使ってみます。ディスパッチを使うには<dispatch/dispatch.h>をincludeしないといけないのですが、Objective-Cで使う場合はFoundationに既に含まれているので何も気にしなくても使えます。

以下、サンプルのソースコードです。単純にディスパッチを試すためC言語だけで組んであります。新規プロジェクトをCommand Line ToolのTypeをCで作成して、main.cを以下のコードに差し替えてください。あっ、もちろんSnow LeopardのXcode3.2以降で実行してください。

#include <stdio.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t gQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(gQueue, ^{
        
        dispatch_queue_t sQueue = dispatch_queue_create("jp.objective-audio.sample1", NULL);
        
        for (int i = 0; i < 10; i++) {
            dispatch_sync(sQueue, ^{
                printf("block %d\n", i);
            });
        }
        
        dispatch_release(sQueue);
        
        printf("finish\n");
        exit(0);
    });
    
    dispatch_main();
    
    return 0;
}

dispatch_asyncで非同期で実行するブロックの中でシリアルキューを作って、10回ほどdispatch_syncでブロックを実行させています。その処理が終わったら”finish”と表示してアプリ終了です。

ブロックの記述は、わざわざ宣言とかしなくても、こんなかんじで関数の中に直接書けちゃいます。

最後のdispatch_main()というのはメインスレッドを休止させるもので、これを使わないとdispatch_asyncでグローバルキューにブロックを実行させる前にアプリがreturn 0で終了してしまいます。NSApplicationMain()かCFRunLoopRun()が走っている場合には必要ありません、というかdispatch_main()を呼び出すと固まります。

キューの種類を入れ替えてみたり、syncとasyncを入れ替えてみたりすると、どんな感じで動作するか分かるかと思います。asyncのところをsyncにするとdispatch_main()が無くてもちゃんと全部実行されますし。逆にsyncのところをasyncにすると、ブロックの処理が別スレッドに行ってしまうので、実行される前に”finish”となります。

とりあえず今回は以上です。asyncやsync以外にもディスパッチでブロックを実行させる関数があるので、また次回見ていきます。

Grand Central Dispatch その2 Queue」への1件のフィードバック

  1. ピンバック: Objective-Cにおける非同期処理についてちょいと調べた | nadegiri,app

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です