CFRunLoopSourceの使い方その3 [考え中の問題]
先日までで歴史のお話は終わり。これから比較的低レベルでのスレッド操作のための材料を集める。
とりあえずさくっとスレッド間での通信(同期や排他制御と言った情報のやり取りのためのメカニズム)をおさらいしておく。スレッドそのものの生成や属性設定は前やったのでここではわかっていることにする。
昔のunixにはプロセス間通信なら
ここ十数年ぐらいの間にプログラミングでのスレッドの重要性が増してたので、スレッドのプログラミングを簡単にするためにPOSIX標準としてスレッドの生成破棄なんかといっしょにスレッド間通信も標準化された。pthreadと言う。スレッド間通信と言ってもプロセスと違って通信そのもののメカニズムが必要なわけではない。実質的に排他制御の機能を持っているだけである。
さらに、MacOS XではCore Foundationのレベルで使うことのできるいくつかのスレッド間通信のための部品が用意されている。
例えばフラグを操作するとき
ここではflagという変数をチェックしてtrueなら反転する、という操作をしているが、その間に他のスレッドがflag変数アクセスしないようにできるので、チェックしたあとたまたま他のスレッドで値が変更されてしまって、そのあとfalseにしてしまう、などと言うことを避けられる。
ただし、他のスレッドもflag変数を操作するときは同じようにmutexに対する操作で囲まなければならない。ロックは単に「ロックする」という動作そのものをひとつのスレッドだけでできるようにするメカニズムであって、特定の変数(ここではflag変数)を自動的に排他制御するわけではない。つまりlockとunclockに囲まれたところがひとつのスレッドだけで実行されるというわけではない。
例えば別のスレッドに
このコードはさっきの(2)と(3)の間に実行される可能性がある。この変数の排他制御をしたいならこっちのスレッドでも
これが一番基本的な使い方だけど、pthreadのロックはいろいろなことができる高機能なものになっている。そのため若干オーバーヘッドが大きい、とも言える。
もちろん排他制御というのはなんでもそうだけど、ちゃんと使わないとデッドロック、つまり複数のスレッドがロックを奪い合って何もしなくなるという状態を起こす。ロックを使うと必ずデッドロックするなら、まだデバグもしやすいんだけど、他のスレッドの状態によってデッドロックになったりならなかったりということも起こりえる。
まだあるけど、残りはまた明日にする。寝る。
3 一般的なスレッド間通信
ということで、最近のCPUに積まれている複数のコアを使い倒すにはスレッドのプログラミングが重要である、という話であった。しかしプロセスと違って同じ仮想空間を共有するのでメモリアクセスにはシングルスレッドや複数プロセスのプログラミングとは違った技術が必要になる。さらに十分マルチコアの性能を引き出すためのプログラミングは、問題によっては非常に難しい場合がある。とりあえずさくっとスレッド間での通信(同期や排他制御と言った情報のやり取りのためのメカニズム)をおさらいしておく。スレッドそのものの生成や属性設定は前やったのでここではわかっていることにする。
昔のunixにはプロセス間通信なら
- シグナル
- パイプ
- セマフォ
- 共有メモリ
- RPC(ソケットなどを使った)
ここ十数年ぐらいの間にプログラミングでのスレッドの重要性が増してたので、スレッドのプログラミングを簡単にするためにPOSIX標準としてスレッドの生成破棄なんかといっしょにスレッド間通信も標準化された。pthreadと言う。スレッド間通信と言ってもプロセスと違って通信そのもののメカニズムが必要なわけではない。実質的に排他制御の機能を持っているだけである。
さらに、MacOS XではCore Foundationのレベルで使うことのできるいくつかのスレッド間通信のための部品が用意されている。
- pthreadによる排他制御
- Atomic Operation(不可分操作)やその他の代替
- 実行ループソース
3.1 pthread ロック
pthreadのロックはCではよく使われる排他制御の手段である。さっきも書いたがPOSIXで比較的新しく標準化されたため、システムコールとしての互換性は比較的高い。例えばフラグを操作するとき
int flag; pthread_mutex_t mutex; pthread_mutex_init(&mutex,NULL); // (1) .... pthread_mutex_lock(&mutex); // (2) if (flag) flag = false; pthread_mutex_unlock(&mutex); // (3)などとやる。この(2)のlockと(3)のunlockに挟まれたコードは、ひとつのスレッドだけでしか実行されないようにすることが可能になる。
ここではflagという変数をチェックしてtrueなら反転する、という操作をしているが、その間に他のスレッドがflag変数アクセスしないようにできるので、チェックしたあとたまたま他のスレッドで値が変更されてしまって、そのあとfalseにしてしまう、などと言うことを避けられる。
ただし、他のスレッドもflag変数を操作するときは同じようにmutexに対する操作で囲まなければならない。ロックは単に「ロックする」という動作そのものをひとつのスレッドだけでできるようにするメカニズムであって、特定の変数(ここではflag変数)を自動的に排他制御するわけではない。つまりlockとunclockに囲まれたところがひとつのスレッドだけで実行されるというわけではない。
例えば別のスレッドに
flag = true;というコードがあったとする。
このコードはさっきの(2)と(3)の間に実行される可能性がある。この変数の排他制御をしたいならこっちのスレッドでも
pthread_mutex_lock(&mutex); // (4) flag = true; pthread_mutex_unlock(&mutex);としなければならない。このコードだと、もしさっきの(2)と(3)の間に実行されようとしたら、(4)で実行がブロックされる。ロック変数(ここではmutex)はひとつのスレッドでだけロックが可能であるためである。そして(3)が実行されると(4)のブロックが解かれて変数の値が書き換えられる。
これが一番基本的な使い方だけど、pthreadのロックはいろいろなことができる高機能なものになっている。そのため若干オーバーヘッドが大きい、とも言える。
もちろん排他制御というのはなんでもそうだけど、ちゃんと使わないとデッドロック、つまり複数のスレッドがロックを奪い合って何もしなくなるという状態を起こす。ロックを使うと必ずデッドロックするなら、まだデバグもしやすいんだけど、他のスレッドの状態によってデッドロックになったりならなかったりということも起こりえる。
まだあるけど、残りはまた明日にする。寝る。
2011-02-04 21:33
nice!(0)
コメント(0)
トラックバック(0)
コメント 0