atomic operation どころか mutex もわかってなかったという話

read write lock ってものがあります。 pthread だと pthread_rwlock_t 。コレの私の思ってたセマンティクスは以下のようなもんです。

  • writer lock を取ると普通の mutex lock みたいな感じ。その thread が unlock するまで、以降の reader lock と writer lock は block する。
  • reader lock を取ると、それ以降の writer lock は block する。 reader lock は block せずに取れる

これは glibc の pthread のデフォルトの挙動なんですが、これは変えられるし、 POSIX によると環境によって違う挙動をすることもあるらしい、ってのを知りませんでした。

どういう時の挙動が変わるかというと、 reader lock が取られてて、 writer lock が待ってる時に次の reader lock をどう扱うか、っていう部分で、 writer lock が reader lock の終了を待ってる時は次の reader lock を block する、って挙動がありえるそうです。これはなんのためかというと、 reader がうじゃうじゃいて常に lock 取ってたりすると、永久に writer の順番が回ってこないという問題が回避できる、と。こういう問題を writer starvation というらしいです。

glibc の pthread では、 pthread_rwlockattr_setkind_np に PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP を渡してやると、 writer starvation を回避する挙動になるとのこと。こっちの挙動の欠点は、名前に書いてある通り、 recursive な lock が deadlock に大変なりやすくなることで、

  • thread1 が reader lock を取る
  • thread2 が writer lock を取ろうとして待ちはじめる
  • thread1 が reader lock を再び取ろうとするけど、 thread2 が待ってるから reader lock でも block されちゃう

って感じで簡単に deadlock しちゃいます。というか、今回このへん勉強してみたのはまさにこの deadlock をやらかしてしまったからでした。 reader lock は適当に同じスレッドでいくら取ってもいい、って理解だったので、共有物の getter 関数ぽいのは適当に全部 reader lock でくるんでおいて、 reader lock 取られてる中でも深く気にせずにその関数を読んでたところ、上述のシナリオになってしまった、という。

あと、 glibc の pthread は PTHREAD_RWLOCK_PREFER_WRITER_NP てのも用意してるけど、色んなとこの情報と glibc-2.7 のコード見る感じでは、名前に反してデフォルトの PTHREAD_RWLOCK_PREFER_READER_NP と同じ挙動のようでした。

まあ、たいていの場合は PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP を使って、たまにあるアップデート時に writer lock するけど、そのアップデート自体はいつでもいいよ、みたいなものならデフォルトのを使う、みたいなのがいい感じなんでしょうか。

適当に参考にしたもの。前者はすごい詳しい。

http://www.tsoftware.jp/nptl/

http://stackoverflow.com/questions/2190090/how-to-prevent-writer-starvation-in-a-read-write-lock-in-pthreads

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h