2010/12/15

Grand Central Dispatch

最近、研究のPCのプログラムをiPhoneに移植することに挑戦していてマルチスレッドプログラミングを授業とかでやったが実践で使うのは初めてで、色々と勉強になったから 参考になったもののまとめとしてこの記事を書くことにした。
色々と忘れそうで...

以下GCDと呼ぶGrand Central DispatchはMacOS 10.6から使えてブロックを使いまくるライブラリです( 笑)WWDC2009に参加したときブロックの話が多くてイメージしていたが実際に使う機会がなくて やはり研究の様な重い処理や時間が掛かる様なものじゃないと使うまでもないし-[NSObject performSelectorOn...]NSOperationなどがあって低レベルのブロックを普段使うまでもありませんね。
WWDC2010ではGCDで盛り上がった.GCDはオプンソースになって現時点でMacOSとiOS以外に実装したOSはFreeBSDのみ

特に参考になったサイトは:
などなど


では、

インポート

まず、GCDはフレームワークではない、dylibなので
#import <dispatch/dispatch.h>
と書くだけでGCDのAPIが使える。

キューの作成

  • メインキュー : メインスレッドで実行
    dispatch_queue_t main = dispatch_get_main_queue();

  • グローバルキュー : バックグラウンドで実行
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    //priories:
    //DISPATCH_QUEUE_PRIORITY_HIGH
    //DISPATCH_QUEUE_PRIORITY_DEFAULT
    //DISPATCH_QUEUE_PRIORITY_LOW

  • プライベートキュー : バックグラウンドで実行、名前付きのキュー
    dispatch_queue_t queue = dispatch_queue_create("com.nacho4d.myApp.myQueue",NULL);
    デフォルトではプライオリティは DISPATCH_QUEUE_PRIORITY_DEFAULT だが変えることが出来る:
    dispatch_queue_t high = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,NULL);
    dispatch_set_target_queue(queue,high);
    

    他のタイプのキューとの大きな違いはブロックや関数が逐次的に実行されていくこと。他のキューではタスクをエンキューしても実際にどの順番で実行されるかが分からないが、プライベートなキューでは必ずキューの頭からタスクを実行していくのでシリアルなキューを実現できる。
    プライベートなキューを作成するとRetainカウントが1なので、 最後にリリースをすることを忘れずに
    dispatch_release(high);
    

キューの停止

停止できるが、実行中のブロックを止めることができません。停止がその次のブロックから適用される、つまりNSOperationQueueのsetSuspended:と全く同じ
dispatch_suspend(queue);

非同期と同期

実行するキューを指定し、ブロックを非同期的にまたは同期的に実行
dispatch_async(queue,^{/* 非同期 */});
dispatch_sync(queue,^{/* 同期 */});
例:
dispatch_queue_t queue = dispatch_queue_create(“com.app.task”,NULL)
dispatch_queue_t main = dispatch_get_main_queue();

dispatch_async(queue,^{
 CGFLoat num = [self doSomeMassiveComputation];

 dispatch_async(main,^{
  [self updateUIWithNumber:num];
 });
});
または
dispatch_queue_t queue = dispatch_queue_create(“com.app.task”,NULL);

__block CGFloat num = 0;

dispatch_sync(queue,^{
 num = [self doSomeMassiveComputation];
});

[self updateUIWithNumber:num];

ブロックを後で実行

-[NSObject performSelector:afterDelay]などのメソッドの代わりに使えるdispatch_after関数がある.利点:メソッドは不要

void dispatch_after(
   dispatch_time_t when,
   dispatch_queue_t queue,
   dispatch_block_t block);

例:0.3秒後にメインスレッドでブロックを実行する(UIKitのUITextViewを扱うのでメインスレッドに変えている).
dispatch_after(300, dispatch_get_main_queue(), ^{
        UITextView *textView = (UITextView *)[controller.view viewWithTag:12345];
        [textView removeFromSuperview];
        textView = nil;
    });

Forループの並列化

重い処理を分けて行って高速化を!
void dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
例えば、CoreVideoのイメージバッファーの中から特定なチャネルだけコピーしたい場合は普段このように行うでしょう:
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
size_t width = CVPixelBufferGetWidth(imageBuffer); 
size_t height = CVPixelBufferGetHeight(imageBuffer); 

char *imageData = llahProcessor.inputImageDataBaseAddress;
int stepWidth = width*3; //stepWith of output image
for (int j = 0; j < height; j++) {
 for (int i = 0; i < width; i++) {
  int out_pixIndex = j*stepWidth + i*3;
  int in_pixIndex = j*bytesPerRow +i*4;  
  imageData[out_pixIndex]   = baseAddress[in_pixIndex]; //B
  imageData[out_pixIndex+1] = baseAddress[in_pixIndex+1]; //G
  imageData[out_pixIndex+2] = baseAddress[in_pixIndex+2]; //R
 }
}

しかし、dispatch_apply関数を用いて並列化を行うことができます:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_apply(height, queue, ^(size_t j) {
   for(int i = 0; i < width; ++i ) {
    int out_pixIndex = j*stepWidth + i*3;
    int in_pixIndex = j*bytesPerRow +i*4;
    imageData[out_pixIndex]   = baseAddress[in_pixIndex]; //B
    imageData[out_pixIndex+1] = baseAddress[in_pixIndex+1];//G
    imageData[out_pixIndex+2] = baseAddress[in_pixIndex+2];//R
   }
});
GCD並列処理を行っているので理論上では早いですが、例えば640x480の画像でforループの480個を同時に走らせとうしている!つまり実際に並列処理をするには480個のキューが必要だがそんな膨大な数のキューを作るには少し時間が掛かるしおそらく全部作れないのでエンキューされるforループのあるでしょうし 640x480の画像を処理を一瞬で終わるのでこの例は現実的ではないですえ。dispatch_apply関数の使い方だけに着目してください。マルチスレッドの話は難しくて長過ぎて教科書を読むが一番ですね

シングルトン

Colin Wheeler氏が指摘するようにブロックを使ったシングルトンの方がマルチスレッドセフティでその為に dispatch_onceを使います. dispatch_onceはアプリケーションが実行している間に一回のみ実行されると保証してくれる関数で便利ですね。
+(MyClass *)singleton {
 static dispatch_once_t pred;
 static MyClass *shared = nil;
 
 dispatch_once(&pred, ^{
  shared = [[MyClass alloc] init];
 });
 return shared;
}
まぁ〜 これでGCDのことをちょっと忘れたときにちらっと見れば思い出すかと ... うむ

0 comments :