recursive mutex

mutexはスレッド間の排他処理をするための機構だが,一般的なthreadライブラリのデフォルトのmutexには大きな問題がある.あるmutexを既にlockしているthreadが再度同じmutexをlockしようとするとブロックしてしまうのだ.たとえば,thread bufferの例で,emptyを判定する部分を別メソッドにして,こちらもlockをかけるように書き直してみる.

class buffer {
    list<int> queue;
    boost::condition_variable cond;
    boost::mutex mut;
public:
    bool empty() {
        boost::lock_guard<boost::recursive_mutex> lock(mut);
        return queue.empty();
    }
    ...
    int pop() throw(out_of_range) {
        boost::unique_lock<boost::mutex> lock(mut);
        while (empty())
            cond.wait(lock);
        int tmp = queue.front();
        queue.pop_front();
        return tmp;
     }
};

pop()のなかからempty()を呼び出しているわけだが,pop()ですでにlockしているので,empty()のlockのところでデッドロックする.なんというか自分の靴ひもを自分で踏んで動けなくなる,みたいな感じだ.

私のように,Javaからスレッドを学んだものにはその仕様自体が信じられないのだが,pthread の仕様もそうなのでどうにもならない.で,Javaと同様に,lockしているthreadが再度lockしようとした場合にはlockを許し,lockされた回数と同じ回数だけunlockされると、本当にunlockする構造のmutexをrecursiveなmutexと呼ぶらしい.

これは割に最近pthread の標準にも入ったようなのだが,仕様にcondition variableと組み合わせるとうまく動かないかもしれないから一緒に使うな,的なことが書いてあって,使い物になるのかどうかよくわからない.

で,boostにはrecursiveなmutexもサポートされている.これをつかうためには次のようにプログラムを書き換えればよい.

  • mutex の代わりに mutex_recursive を使う
  • condition_variableの代わりに condition_variable_any を使う
class buffer {
    list<int> queue;
    boost::condition_variable_any cond;
    boost::recursive_mutex mut;
public:
    bool empty() {
	boost::lock_guard<boost::recursive_mutex> lock(mut);
	return queue.empty();
    }
    ...
    int pop() {
	boost::unique_lock<boost::recursive_mutex> lock(mut);
	while (empty())
            cond.wait(lock);
	int tmp = queue.front();
	queue.pop_front();
	return tmp;
    }
};

すばらしい.というかこっちがデフォルトのmutexでもいいような気がするんだがなあ.