_std::unique_lock
_を操作するときの_std::condition_variable
_の役割について少し混乱しています。 documentation を理解している限り、_std::unique_lock
_は基本的に肥大化したロックガードであり、2つのロック間で状態を交換する可能性があります。
私はこれまでpthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
をこの目的に使用しました(これはposixでSTLが使用するものだと思います)。ロックではなく、相互排他ロックを使用します。
ここの違いは何ですか? _std::condition_variable
_が_std::unique_lock
_を扱うという事実は最適化ですか?もしそうなら、それはどのくらい正確ですか?
技術的な理由はありませんか?
彼が技術的な理由を与えたと思うので、私はcmeerwの答えを支持しました。それを見てみましょう。委員会がcondition_variable
をmutex
で待機させることにしたと仮定しましょう。そのデザインを使用したコードは次のとおりです。
void foo()
{
mut.lock();
// mut locked by this thread here
while (not_ready)
cv.wait(mut);
// mut locked by this thread here
mut.unlock();
}
これはまさにshould n'tがcondition_variable
を使用する方法です。でマークされた地域:
// mut locked by this thread here
例外の安全性の問題があり、それは深刻な問題です。これらの領域で(またはcv.wait
自体によって)例外がスローされると、例外をキャッチしてロックを解除するためにtry/catchがどこかに配置されない限り、mutexのロック状態がリークされます。しかし、それはプログラマーに書くように頼んでいるより多くのコードです。
プログラマーが例外安全なコードを書く方法を知っており、それを達成するためにunique_lock
を使用することを知っているとしましょう。これで、コードは次のようになります。
void foo()
{
unique_lock<mutex> lk(mut);
// mut locked by this thread here
while (not_ready)
cv.wait(*lk.mutex());
// mut locked by this thread here
}
これははるかに優れていますが、それでも素晴らしい状況ではありません。 condition_variable
インターフェースは、プログラマーが物事を機能させるために邪魔にならないようにします。 lk
が誤ってmutexを参照しない場合、nullポインターの逆参照が発生する可能性があります。また、condition_variable::wait
がこのスレッドがmut
のロックを所有しているかどうかを確認する方法はありません。
ああ、思い出してください、プログラマーが間違ったunique_lock
メンバー関数を選択してmutexを公開する危険性もあります。 *lk.release()
はここでは悲惨なものになるでしょう。
次に、condition_variable
を使用する実際のunique_lock<mutex>
APIを使用してコードを記述する方法を見てみましょう。
void foo()
{
unique_lock<mutex> lk(mut);
// mut locked by this thread here
while (not_ready)
cv.wait(lk);
// mut locked by this thread here
}
wait
関数はlk.owns_lock()
をチェックし、false
の場合は例外をスローできます。これらは、condition_variable
のAPI設計を推進した技術的な理由です。
また、condition_variable::wait
は、lock_guard<mutex>
があなたの言うとおりであるため、lock_guard<mutex>
は取りません。lock_guard<mutex>
が破棄されるまで、このmutexのロックを所有しています。ただし、condition_variable::wait
を呼び出すと、ミューテックスのロックが暗黙的に解除されます。そのため、そのアクションはlock_guard
ユースケース/ステートメントと矛盾します。
とにかくunique_lock
が必要だったので、関数からロックを返し、コンテナに入れて、例外セーフな方法で範囲外のパターンでミューテックスをロック/ロック解除できるので、unique_lock
がcondition_variable::wait
の自然な選択でした。
更新
bamboonは、以下のコメントでcondition_variable_any
と対比することを提案したので、ここに行きます:
質問:condition_variable::wait
がLockable
型を渡すことができるようにテンプレート化されないのはなぜですか?
回答:
それは本当に素晴らしい機能です。たとえば、 このペーパー は、条件変数(posixの世界では前代未聞ですが、それでも非常に便利です)で共有モードでshared_lock
(rwlock)で待機するコードを示しています。ただし、機能はより高価です。
そこで、委員会はこの機能を備えた新しいタイプを導入しました。
`condition_variable_any`
このcondition_variable
でadaptor待機できますanyロック可能タイプ。メンバーlock()
およびunlock()
が含まれている場合は、準備ができています。 condition_variable_any
を適切に実装するには、condition_variable
データメンバーとshared_ptr<mutex>
データメンバーが必要です。
この新しい機能は、基本的なcondition_variable::wait
よりも高価であり、condition_variable
は非常に低レベルのツールであるため、この非常に便利でより高価な機能は、使用する場合にのみ料金を支払うように別のクラスに配置されました。
基本的に、デフォルトでAPIを可能な限り安全にすることがAPIの設計上の決定です(追加のオーバーヘッドは無視できると考えられています)。未加工のmutex
の代わりにunique_lock
を渡すことを要求することにより、APIのユーザーは(例外が存在する場合に)正しいコードを書くことになります。
近年、C++言語の焦点は、デフォルトで安全にすることへとシフトしています(ただし、ユーザーは、必要に応じて十分な努力をして自分の足で撃つことができます)。