From: KOSAKI Motohiro Date: 2011-01-26T00:32:11+09:00 Subject: [ruby-dev:43109] Re: pthread_cond を用いたConditionVariable 2011年1月25日22:09 Yusuke ENDOH : > 遠藤です。 > > 2011年1月25日17:50 KOSAKI Motohiro : >>> の部分は、native_mutex_lock の直前に別スレッドで CV#signal が呼ばれたとき、 >>> signal が無視される気がしました (気のせいかもしれませんが) 。 >>> > *snip* >> ええと、いままではGVL持ってるときは他のロックとるの禁止にしてますよね。 >> GVLスレッドが待ってしまうと、全部止まるから。 > > mutex->lock とかは GVL 持ってる時にロックしてますが、そういう話 > ではない? 痛いところつかれた。あれ不思議なんだよなぁ。lock_func()はGVLはずしてから 読んでるけど、rb_check_deadlock()はGVLを持ちながらmutex->lockをとるという 不思議なつくりが >> 若干危険な香りがします。 > > うーん。危険かなあ。 > 確かに自信はないんですが、どう直せばいいでしょうね。 > それとも signal 漏れしそうなのは私の気のせい? いや、気のせいじゃないと思います。 いまのcv->lockってなんにも守ってない気がします。cond_signalと cond_waitがエラーにならないようお義理で読んでるだけのイメージ native_mutex_lock(cv->lock) rb_mutex_unlock(arg->mutex); native_mutex_wait(cv->lock) って書きたいが、これはこれで危険な香りがするのでちょっと周辺コード読まないとあかんなぁ。 libcのcond_waitはそうしてるんだよね。擬似コードで概略書くと cond_wait(cv, mutex) { lock(cv->lock) mutex_unlock(mutex) pthread_cleanup_push() // pthreadキャンセル用後始末コード do { unlock(cv->lock) sleep lock(cv->lock) } while (val == seq); // sprious wakeupしたらやりなおし unlock(cv->lock) mutex_lock(mutex) } >>> 2 は、CV#wait は return したときに時に引数の mutex をロックしていることが >>> 期待されていますが、非同期例外が投げ込まれたら即座に終了することも期待され >>> ていて、どっちを優先すべきか自明でないという設計上の悩みです。 >>> 個人的には、完全に割り込めないのは kill -9 でしか止められなくて困るので、 >>> 非同期例外を優先すべきだと思っています。 >> >> わたしは逆で、mutexロック待ちになるってのはCV#signalがわがmutexを離さないという >> 状況なんですが、実際にはそんな事しないと思います。まあデバッグ中はミスるかもしれませんが > > Ctrl-C を頻繁にしたい状況なので、デバッグ中がメインです。 > mutex を離さないバグは、CV#signal 周りよりはむしろ、他スレッドの > CV#wait の周りでやりがちな気がします。例えば、 > > m.synchronize do > cv.wait while predicate > # ここで無限ループ、またはとても時間がかる > end > > とか。 > 私が Ruby を好きな理由は、Kernel#p を始め、デバッグが軽快で楽しく > なるようにしているところなので、この程度のことで kill -9 が必要に > なるのは嫌です。 それに対する答えは1つしかなくて、CVなんか使うな。ですよ。 そういう中断できないAPIを選んでるんだから。 気持ちよくCtrl+Cで中断したい人はpipe でスレッド間通信しておけばよかったわけです。 どうしてもCVが使いたければ、プログラムの最初でスレッドつくってmainスレッドはthread.joinするだけ にしとけば、絶対割り込み可能です。これは十分現実的でしょう。 ここで、「Ctrl+Cで割り込まれても大丈夫なように呼び出し側で注意しろよ」とドキュメントに明記したとして 平均レベルのプログラマにはエンバグ必須の無茶APIになってしまうので課題が解決していないのでは ないかと感じます。 >>> CV 以外のわかりやすい Ruby 向けの同期プリミティブはないですかねえ。例えば >>> predicate をブロックに包むだけでも大分わかりやすくなるのではないか。 >> >> ブロックを提供するのには賛成です。 > > こんな感じですかねえ。 > > class SynchronizedQueue > def initialize > @array = [] > @cv = ConditionVariable.new { !@array.empty? } > end > def push(x) > @cv.signal_and_synchronize { @array << x } > end > def pop > @cv.wait_and_synchronize { @array.unshift } > end > end cv.wait 以外はspurious wakeup問題がないので、特に拡張は不要という 認識でした。これは構文の一貫性のためだと思って良いのでしょうか? 見逃しがあったら教えてください。 >>> さらに、CV の API は C API (pthread) のパクリなので、C に存在しない機能 >>> (例外など) との兼ね合いが検討されていないという問題もあります。 >>> 仮に 1 の機能が整備されたとしても、Ctrl-C まで考慮した CV#wait の使い方は >>> かなりややこしくなり、常人には正しく使いこなせない予感がします。 >> >> ぜんぜんドキュメントに注意点が書いてないんですよね。 > > そもそも、注意点を網羅的かつ明確に理解できている人が現時点で存在 > しないという説。 遠藤さん理解してはるじゃないですか・・・・・ >>> いずれも、CV を C 実装に直すだけでどうにかなる話ではないと思います。 >> >> 意図は、1.9.x は非同期例外時にmutexをlockしてる事を保証することでごまかして、 >> 非同期例外の抜本的な拡張は2.0送りじゃないか。というものでした。 >> また、2.0ではCVに変わる代替APIの検討がいるかと思います。 > > 設計の問題に関しては、1.9.x は現状のまま、に一票。 > パフォーマンス改善のために C 化することについては、安定させるのが > 大変そうだけどそれに見合うかなーという疑問を除けば、賛成でも反対 > でもないです。