iOSの非同期処理を提供するDispatch Queuesについて

iPhoneアプリを作成する中で、画像の読み込みや画像の編集など非同期的に処理を行いたい機会が度々ある。手軽に利用できる非同期処理はdispatch_queueを使った処理であるが、queueに追加した処理の実行順序が不透明だったので簡単にまとめる。

以下は次のリファレンスの簡単なまとめである。

Concurrency Programming Guide: Dispatch Queues

dipatch queueについて

dispatch queueはマルチスレッドプログラミングの機能を提供するAPIである。マルチスレッドプログラミングは、マルチコア化の激しいCPUについてアプリケーションの高速化を計る有効な手段である。マルチスレッドプログラミングでは、プログラムをスレッドという単位に切り出してプログラムを同時並行的に実行する。CPUのクロック性能が低くとも同時に処理できる機構が存在すれば見かけ上の実行速度が大きく改善されることになる。

マルチスレッドプログラミングで問題なのは、プログラムをスレッドを管理する必要するという点である。例えば、スレッドを何時作成し、同時にいくつまでスレッドを処理させるのかや、効率的なスレッドのCPUコアへの割り振りなどをする必要がある。これらを自前で実装するとなると、一般に大きな手間になる。その為、application側でこれらの管理機能を提供したのがdispatch queueである。

dispatch queueによって、アプリケーションの開発者は自分のプログラムをどのように並列化すれば良いかだけを考えれば良くなっている。

dispatch queueの種類

dispatch queueは用途に応じて以下の3種類の機能を提供している。

  • Serial

    • private dispatch queueとも。

    • queueに追加したタスクを追加した順番に実行していく。この時、次のタスクが実行されるのは前のタスクが終了した後である。

    • 各タスクはそれぞれ一つのthreadで実行されるが、同じthreadで実行されるとは限らない。

    • queueはいくつでも作成可能である。queueごとに順番に実行され、Queue間のタスクは同時に実行される。つまり、4つのqueueに2つずつタスクを追加すれば、4つずつ実行される。

  • Concurrent

    • gloval dispatch queueとも。

    • queueに追加したタスクを追加した順番に実行していく。但し、前のタスクが終了する前に次のタスクが実行され得る。

    • 同時並行処理されるタスクの数は、指定可能であるが、システムの状態にも依存する。

    • queueは自前で作成も可能だが、defaultでPriorityの異なる4つのglobal queueが提供されている。

  • Main dispatch queue

    • applicationのMain threadを表し、Mainthread上でqueueに追加したTaskを実行する。

    • viewの描画などはMain threadで行われるので専らこの用途で利用される。

    • このqueueは自分で作成することはない。

上記のqueueはそれぞれPriorityを持っており、queueの間での実行順序を決めている。

dispatch queueの例

ここで、簡単な例をあげておく。Concurrentの場合の例が次である。Concurrentの場合はあらかじめ存在する、global queueの参照を取得して利用する。

dispatch_get_global_queueでglobal queueを取得する。

  • 第一引数がqueueのpriorityを表しており、DEFAULT, HIGH, LOW,BACKGROUNDの4つが存在する。

  • 第二引数は、現在利用されていない引数で0を指定するようになっている。

printf("before");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    printf("first async");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    printf("second async");
});
printf("after");

上記のプログラムの実行結果は次のパターンのどれかである。一応起こりやすそうなものから順に並べている。

before
after
first async
second async
before
first async
second async
after
before
first async
after
second async
before
after
second async
first async
before
second async
first async
after
before
second async
after
first async

次に、Serialの場合の例である。Serialの場合は、自分でQueueを作成する。作成するQueueはdispatch_queue_t型である。

dispatch_queue_createメソッドで、queueを作成する。

  • 第一引数は、queueの名前でアプリケーションで一意的な名前を与えるが、名前としてDNS形式のものが推奨されている。

  • 第二引数は、iOS4.3以降であれば、DISPATCH_QUEUE_SERIALもしくはNULLを指定することで、Serialのqueueが作成され、DISPATCH_QUEUE_CONCURRENTと指定するとconcurrentのqueueが作成できる。iOS4.3以前であれば、NULLを指定しなければならない。

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MyQueue", NULL);

printf("before");
dispatch_async(serialQueue, ^{
    printf("first async");
});
dispatch_async(serialQueue, ^{
    printf("second async");
});
printf("after");

上記のプログラムの実行結果は次のパターンのどれかである。一応起こりやすそうなものから順に並べている。

before
after
first async
second async
before
first async
second async
after
before
first async
after
second async

SerialとConcurrentの実行結果の違いを見比べてみると良いだろう。

その他の機能

マルチスレッドプログラミングを行う上で、いくつか便利な機能が提供されている。簡単に紹介しておく。

  • dispatch groups

    • 幾つかのタスクをグループとして纏めて管理することができる。

    • 主な用途は、グループとして纏め上げたタスクが全て実行終了したら知らせを受けるなど。

  • dispatch semaphores

    • Semaphore(セマフォ)の機能を提供している。

    • 主な用途は、あるデータに同時にアクセスできるスレッドの数を制限したりなど。

  • dispatch sources

    • システムのイベントが起こった時に、非同期的に実行するタスクを登録する。

    • システムのイベントの例としては、以下のイベントがある。

      • タイマーによる定期的なイベント

      • UNIX signal受信時

      • fileやsocketの読み込み準備完了などの通知

    • 詳細は Concurrency Programming Guide: Dispatch Sources