SSブログ

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

先日pthreadのロックをおさらいした。基本的な使い方であればそれほど難しくない。今日はその他のスレッド間通信に使える部品のその他について。

3.2  不可分操作Atomic Operation

不可分操作というのは簡単な一連の演算を他のスレッドが割り込まないようにするOSのサービスのことでMacOS Xの場合、/usr/include/libkern/OSAtomic.hに宣言されている。

簡単な一連の操作と言うのは、例えばある変数をインクリメントしたりデクリメントしたり、あるビットをセットしたりリセットしたり、という非常に簡単なものだけど、フラグを操作する場合によくやる演算。これは普通はロックを使って実装するけど、それよりもオーバーヘッドが小さいらしい。

これらの操作はLinuxを含めたunixではだいたい使えるが、pthreadのように標準化されていなくてそれぞれ自分勝手な名前になっている。MacOS XではOSAtomicで始まる関数名になっている。

たとえばさっきのロックを使ったコードを不可分操作で書き換えてみると
    int     flag;
    bool    swapped;
    .....
    swapped = OSAtomicCompareAndSwap32(1, 0, &flag);
    ....
となって、pthreadロックは必要なくなる。この関数はヘッダに
bool    OSAtomicCompareAndSwap32( int32_t __oldValue, int32_t __newValue, volatile int32_t *__theValue );
と定義されていて、*theValueの値がoldValueに等しいとnewValueに書き換えられる。書き換えが起こったらこの関数はtrueを返す。この間、*theValueは他のスレッドからアクセスされないことが保証される。かんたんでいいけど、あまり複雑な操作はなくて、こういったフラグ操作ぐらいしかできない。まあ、たいていの場合それで十分ではある。

ちなみにMacOS Xの場合、インクルード先のディレクトリがlibkernになっているので、kernel space用みたいに思えるけど、これはuser space用で、普通のアプリでも使える。kernel用には別のもの(名前は同じなので紛らわしい)が用意されている。

3.3  volatile変数

マルチスレッド環境では普通の場合問題ないことが大きなバグにつながることがある。ロックで実現不可能ではないけど、面倒で効率を落とすことになる特有の問題に対処するためのメカニズムが用意されている。まずvolatile変数。

sdccでもあったけど、最近のコンパイラは賢くて無駄な操作はやらなかったり、結果に無関係な演算の順番にはこだわらないオブジェクトを出力する。

例えば同じ変数に複数回代入するコードがあったとすると最初の代入だけ実行して他はなにもやらない。つまり
    flag = true;
    ....
    flag = true;
とあったとしても、これが同じ関数内で、かつ途中に他の関数呼び出しがない場合、コンパイラは2回目以降を無視したプロセサ命令を出力する。

ところがたとえばこの途中で他のスレッドがflag変数を書き換える可能性がある場合、2回めの代入には意味がある。

sdccにも同じキーワードがあったがMacOS Xでも
    volatile    bool    flag;
と宣言すれば、上のようなことは避けられる。

3.4  メモリ障壁Memory Barrier

最近のプロセサは複数の実行ユニットを持っていて命令キューから用意のできた命令を取り出して同時実行する。「用意のできた命令」というのは「キューの中でそれよりも前に依存する命令がない」という状態の命令のことで、「依存する命令」というのは例えばある命令を実行した結果を使うような場合で、その命令を待たなければいけない。「用意のできた命令」というのはようするにいつ実行してもいいとみなされる。こうすることによってプロセサはなるべくいつも何か仕事をしているようになって実行効率が上がる。これを「アウトオブオーダー実行out-of-order execution」という。

例えば一つのスレッド-1で
    y = 1;
    flag = true;
とある場合。つまりスレッド-1が変数yの値を書き換えて、それをflag変数で表しているとする。そしてもうひとつのスレッド-2で
    if (flag == true)
        x = y;
のようにflag変数をみて変数yの値をコピーするようなコードを書いたとする。スレッド-1のプロセサがアウトオブオーダー実行していると、yへの代入とflagへのセットの順番が入れ替わる可能性がある、ということになる。そのときスレッド-2では古いyの値がxにコピーされることになる。

例えば必ず順番に実行して欲しい場合
    y = 1;
    OSMemoryBarrier();
    flag = true;
とする。こうするとyへの代入とフラグのセットの順番が保証され、スレッド-2では正しいyの値がコピーされる。

これは入れ替わりがあったらまずいところをロックしてもいい。上の例では2カ所のそれぞれ2行をロックすればいいけど、それがループの深いところにあったとするとロック操作のオーバーヘッドは無視できなくなる。その場合メモリ障壁を使えるようにするとオーバーヘッドを最小限に抑えることができる。

ロックやvolatile変数やメモリ障壁といった操作はとうぜん効率を低下させる。せっかくマルチスレッドを使ってマルチコアをフルに使おうと思ってもコア数に比例した効率にならない原因の一つにこのような問題がある。スレッドは可能な限り依存性を減らすという工夫が必要となる。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

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

トラックバック 0

献立02/07献立02/08 ブログトップ

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