CFRunLoopSourceの使い方その8 [考え中の問題]
数日あくだけで何やってたか忘れる。今回は簡単なので思い出しやすい。
CFRunLoopやCFRunLoopSourceはどうか、というとReferenceには何も書いてなくて素直に読めば問題ないはずなんだけど、NSRunLoopがダメでCFRunLoopはOKというのもおかしな気がする。もともと違うスレッドからある作業を実行してもらうためのメカニズムなのでその属性をいろいろなスレッドから行う、ということは考えにくいのでスレッドセーフである必要性はあまりないはずである。
ただしCFRunLoopSourceSignalだけはスレッドセーフ(というかリエントラント)であって欲しいな。
名前をrunLoopKickerにしよう。Core Foundationの流儀に従ってOpaqueな構造体で
コールバック関数の形は
まず構造体作成の実装は
コールバックを起動するkickPerformは
ひょっとするとロックは必要ないかもしれない。AppleのドキュメントにはNSRunLoopがスレッドセーフでないことは書かれているが、CFRunLoopやCFRunLoopSourceがどうなのかはよくわからない。Darwinとして公開されているので、CFRunLoopSourceの関数がリエントラントかどうかをソースで確認すればいいんだけど、それはやっぱり大変。たぶんCFRunLoopの方も見なくちゃならないだろうし、NSRunLoopの機能を呼んでいたらもう追えなくなるし。ということで、今回の使い方ならロックのオーバーヘッドはそれほど目立たないので、このまま置いておくのが安全。
どっちにしても使い方から考えて作業を起動するのはひとつのスレッドからだけになるだとは思うので、複数のスレッドからこのkickPerformが呼ばれることはなく、その場合ロックは不要なんだけど。
領域解放の方は作ったときの逆をやればいいだけで簡単だけど、とりあえず書いておく。
この中でCFRunLoopRemoveSourceを呼ぶ必要があるかどうか、はよくわからない。リファレンスを見ればCFRunLoopSourceInvalidateがCFRunLoopRemoveSourceを呼ぶと書いてあるようにみえる。CFRunLoopSourceInvalidateのあとにCFRunLoopRemoveSourceを呼んでもなんとも言われないが、たぶん呼ばなくてもいいということだろう。
4.8 スレッド安全性
基本的にはCore Foundationのオブジェクトはスレッドセーフらしい。Cocoaのオブジェクトも基本的にはスレッドセーフだけど、NSRunLoopはダメとなっている。ようするに他人の(別スレッドの)実行ループを直接さわってはいけない、ということで当然という感じはする。CFRunLoopやCFRunLoopSourceはどうか、というとReferenceには何も書いてなくて素直に読めば問題ないはずなんだけど、NSRunLoopがダメでCFRunLoopはOKというのもおかしな気がする。もともと違うスレッドからある作業を実行してもらうためのメカニズムなのでその属性をいろいろなスレッドから行う、ということは考えにくいのでスレッドセーフである必要性はあまりないはずである。
ただしCFRunLoopSourceSignalだけはスレッドセーフ(というかリエントラント)であって欲しいな。
4.9 CFRunLoopSourceのCラッパ
ということでCFRunLoopSourceを簡単に使えるようにするCのラッパを考えてみる。この役割はCore FoundationのレベルでCFRunLoopSourceがスレッドセーフでないと仮定したときに、もっとも簡単な使い方ができるようにするためのものである。ほんとはCocoaのラッパを作るべきなんだろうけど、それならNSObjectのperform????メソッドを使えばいいのでそんなラッパに意味はなくなる。名前をrunLoopKickerにしよう。Core Foundationの流儀に従ってOpaqueな構造体で
- 構造体の作成と破棄
- 異なるスレッドから指定したコールバックを起動する
runLoopKicker *createRunLoopKicker(performFunction pFunc, void *refcon, CFRunLoopRef onWhichThread); void deallocateRunLoopKicker(runLoopKicker *kck); void kickPerform(runLoopKicker *kck);という関数を定義する。createRunLoopKickerは構造体を確保して初期化するもの、deallocateRunLoopKickerは破棄するもの、kickPerformはコールバックの起動をするためのものである。とりあえずこれだけできれば最も基本的な使い方はできることになる。
コールバック関数の形は
typedef void (*performFunction)(void *refCon);というような一番簡単なものでいいだろう。 そしてrunLoopKicker構造体は
typedef struct runLoopKickerStruct { pthread_mutex_t mutex; CFRunLoopSourceRef source; CFRunLoopRef onRL; } runLoopKicker;としよう。CFRunLoopSourceのオブジェクトを保持する。その他に排他制御用のpthreadのロックと、どのCFRunLoopでコールバックを起動するかを変数として持たせる。
まず構造体作成の実装は
runLoopKicker *createRunLoopKicker(performFunction pFunc, void *refcon, CFRunLoopRef onWhichThread) { CFRunLoopSourceContext context; runLoopKicker *kck; // (1) kck = (runLoopKicker *)malloc(sizeof(runLoopKicker)); if (kck == NULL) return NULL; context.version = 0; // (2) context.info = refcon; context.perform = pFunc; context.cancel = NULL; context.copyDescription = NULL; context.equal = NULL; context.hash = NULL; context.release = NULL; context.retain = NULL; context.schedule = NULL; kck->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); if (kck->source == NULL) { // (3) free(kck); return NULL; } if (onWhichThread == NULL) // (4) kck->onRL = CFRunLoopGetCurrent(); // runLoop on current thread else kck->onRL = onWhichThread; CFRunLoopAddSource(kck->onRL, kck->source, runLoopKickerMode); pthread_mutex_init(&(kck->mutex), NULL); // (5) return kck; }ちょっと長ったらしくみえるけど、まず(1)で構造体の領域を確保する。(2)ではCFRunLoopSourceに渡すcontext変数を設定している。たくさんあるけど、意味があるのはrefcon変数とコールバック関数の設定だけ。もちろんversionは0でなければならない。CFRunLoopSourceのオブジェクトがちゃんとできなかったら領域を解放してNULLを返す。(4)はCFRunLoopの指定にNULLがわたってきたら呼ばれたスレッドのCFRunLoopでコールバックを実行するとみなすことにする。そして(5)でCFRunLoopSourceをそのCFRunLoopに組み込んだあと、pthreadのロックを作って終わる。
コールバックを起動するkickPerformは
void kickPerform(runLoopKicker *kck) { pthread_mutex_lock(&(kck->mutex)); // (6) CFRunLoopSourceSignal(kck->source); // (7) CFRunLoopWakeUp(kck->onRL); // (8) pthread_mutex_unlock(&(kck->mutex));// (9) }とする。(6)と(9)で、CFRunLoopSourceがリエントラントでなかった場合の対策のためにロックで囲んでいる。(7)がコールバックを起動する関数である。(8)は起動先のCFRunLoopがスリープ状態にある場合もあるので起こしてやっている。これだけ。これを呼べば指定されたスレッドでコールバックが呼ばれることになる。
ひょっとするとロックは必要ないかもしれない。AppleのドキュメントにはNSRunLoopがスレッドセーフでないことは書かれているが、CFRunLoopやCFRunLoopSourceがどうなのかはよくわからない。Darwinとして公開されているので、CFRunLoopSourceの関数がリエントラントかどうかをソースで確認すればいいんだけど、それはやっぱり大変。たぶんCFRunLoopの方も見なくちゃならないだろうし、NSRunLoopの機能を呼んでいたらもう追えなくなるし。ということで、今回の使い方ならロックのオーバーヘッドはそれほど目立たないので、このまま置いておくのが安全。
どっちにしても使い方から考えて作業を起動するのはひとつのスレッドからだけになるだとは思うので、複数のスレッドからこのkickPerformが呼ばれることはなく、その場合ロックは不要なんだけど。
領域解放の方は作ったときの逆をやればいいだけで簡単だけど、とりあえず書いておく。
void deallocateRunLoopKicker(runLoopKicker *kck) { pthread_mutex_lock(&(kck->mutex)); CFRunLoopSourceInvalidate(kck->source); CFRelease(kck->source); pthread_mutex_unlock(&(kck->mutex)); pthread_mutex_destroy(&(kck->mutex)); free(kck); }ここでもいちおうロックを呼んでいる。kickPerformを実行中にdeallocateRunLoopKickerが呼ばれなければ不要である。
この中でCFRunLoopRemoveSourceを呼ぶ必要があるかどうか、はよくわからない。リファレンスを見ればCFRunLoopSourceInvalidateがCFRunLoopRemoveSourceを呼ぶと書いてあるようにみえる。CFRunLoopSourceInvalidateのあとにCFRunLoopRemoveSourceを呼んでもなんとも言われないが、たぶん呼ばなくてもいいということだろう。
2011-02-14 22:11
nice!(0)
コメント(0)
トラックバック(0)
コメント 0