SSブログ

CFRunLoopSourceの使い方その8 [考え中の問題]

数日あくだけで何やってたか忘れる。今回は簡単なので思い出しやすい。

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な構造体で
  • 構造体の作成と破棄
  • 異なるスレッドから指定したコールバックを起動する
をするためのものである。最終的には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を呼んでもなんとも言われないが、たぶん呼ばなくてもいいということだろう。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立02/14献立02/15 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。