1. ホーム
  2. c++

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

2023-08-16 13:26:21

質問

の役割について少し混乱しています。 std::unique_lock で作業しているときに std::condition_variable . 私が理解した限りでは ドキュメント , std::unique_lock は基本的に肥大化したロックガードで、2つのロック間で状態を入れ替えることが可能です。

私はこれまで pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) を使用してきました(posixではSTLはこれを使用するのでしょう)。これはロックではなく、ミューテックスを取ります。

ここでの違いは何でしょうか?というのは std::condition_variable が扱うのは std::unique_lock を最適化するのですか?もしそうなら、具体的にどのように速くなるのでしょうか?

どのように解決するには?

<ブロッククオート

技術的な理由はないのですか?

cmeerwさんの回答は技術的な理由を述べていると思うので、upvotedしました。 それを見ていきましょう。 委員会が、以下のように決定したと仮定しましょう。 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();
}

これはまさに、ある はいけません。 を使用します。 condition_variable . でマークされた領域では

// mut locked by this thread here

には例外安全性の問題があり、それは深刻なものです。 もしこれらの領域で例外が投げられたら(あるいは cv.wait 自体で)例外が発生した場合、例外をキャッチしてロックを解除するための try/catch がどこかに組み込まれていなければ、ミューテックスのロック状態が漏えいしてしまいます。 しかし、それはプログラマーに書かせるコードを増やすだけです。

プログラマが例外安全コードの書き方を知っていて、例外をキャッチするために 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 メンバ関数を選んでしまうという危険もあります。 *lk.release() はここで悲惨なことになります。

では、実際にどのようにコードが書かれているのかを見てみましょう。 condition_variable を受け取る API がどのように書かれているかを見てみましょう。 unique_lock<mutex> :

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 .

のAPI設計を推進した技術的な理由です。 condition_variable .

さらに condition_variable::waitlock_guard<mutex> なぜなら lock_guard<mutex> はどのように言うのでしょう。 私はこのミューテックスのロックを lock_guard<mutex> が破壊されるまで、このミューテックスのロックを所有します。 しかし、あなたが condition_variable::wait を呼び出すと、暗黙のうちにミューテックスのロックを解放してしまいます。 ですから、その動作は lock_guard のユースケース/ステートメントと矛盾します。

私たちが必要としたのは unique_lock これは、関数からロックを返したり、コンテナに入れたり、非スコープのパターンでミューテックスを例外的に安全にロック/アンロックしたりできるようにするためで、そのため unique_lock は自然な選択でした。 condition_variable::wait .

更新

bamboonが下のコメントで、私が対照的であることを提案しました。 condition_variable_any を対比させることを提案されたので、それを実行します。

質問です。 なぜ condition_variable::wait はテンプレート化されていないので、任意の Lockable を渡すことができるのですか?

回答

それは本当にクールな機能です。 たとえば この論文 で待機するコードを示しています。 shared_lock (rwlock)を条件変数上で共有モードで待つコードを実演しています (posixの世界では前代未聞ですが、それでも非常に有用です)。 しかし、この機能はより高価です。

そこで委員会はこの機能を持つ新しい型を導入しました。

`condition_variable_any`

このように condition_variable アダプタ を待つことができます。 任意の ロック可能な型。 もしそれがメンバー lock()unlock() であれば、問題ありません。 の適切な実装は condition_variable_any を必要とします。 condition_variable データメンバと shared_ptr<mutex> のデータメンバがあります。

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