web-dev-qa-db-ja.com

C ++ 11:なぜstd :: condition_variableはstd :: unique_lockを使用するのですか?

_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_を扱うという事実は最適化ですか?もしそうなら、それはどのくらい正確ですか?

70
lucas clemente

技術的な理由はありませんか?

彼が技術的な理由を与えたと思うので、私はcmeerwの答えを支持しました。それを見てみましょう。委員会がcondition_variablemutexで待機させることにしたと仮定しましょう。そのデザインを使用したコードは次のとおりです。

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'tcondition_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
}
  1. このコードは、できるだけ簡単です。
  2. それは例外安全です。
  3. 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_lockcondition_variable::waitの自然な選択でした。

更新

bamboonは、以下のコメントでcondition_variable_anyと対比することを提案したので、ここに行きます:

質問:condition_variable::waitLockable型を渡すことができるようにテンプレート化されないのはなぜですか?

回答:

それは本当に素晴らしい機能です。たとえば、 このペーパー は、条件変数(posixの世界では前代未聞ですが、それでも非常に便利です)で共有モードでshared_lock(rwlock)で待機するコードを示しています。ただし、機能はより高価です。

そこで、委員会はこの機能を備えた新しいタイプを導入しました。

`condition_variable_any`

このcondition_variableadaptor待機できますanyロック可能タイプ。メンバーlock()およびunlock()が含まれている場合は、準備ができています。 condition_variable_anyを適切に実装するには、condition_variableデータメンバーとshared_ptr<mutex>データメンバーが必要です。

この新しい機能は、基本的なcondition_variable::waitよりも高価であり、condition_variableは非常に低レベルのツールであるため、この非常に便利でより高価な機能は、使用する場合にのみ料金を支払うように別のクラスに配置されました。

98
Howard Hinnant

基本的に、デフォルトでAPIを可能な限り安全にすることがAPIの設計上の決定です(追加のオーバーヘッドは無視できると考えられています)。未加工のmutexの代わりにunique_lockを渡すことを要求することにより、APIのユーザーは(例外が存在する場合に)正しいコードを書くことになります。

近年、C++言語の焦点は、デフォルトで安全にすることへとシフトしています(ただし、ユーザーは、必要に応じて十分な努力をして自分の足で撃つことができます)。

34
cmeerw