web-dev-qa-db-ja.com

なぜstd :: condition_variableが必要なのですか?

_std::condition_variable_は、誤ったウェイクアップのために使用するのが非常に難しいことがわかりました。そのため、次のようなフラグを設定する必要がある場合があります。

_atomic<bool> is_ready;
_

_is_ready_をtrueに設定してからnotify(notify_one()またはnotify_all())を呼び出してから、待機します。

_some_condition_variable.wait(some_unique_lock, [&is_ready]{
    return bool(is_ready);
});
_

私がこれをするだけではいけない理由はありますか?(編集:OK、これは本当に悪い考えです。)

_while(!is_ready) {
    this_thread::wait_for(some_duration); //Edit: changed from this_thread::yield();
}
_

_condition_variable_が待機時間を選択した場合(これが本当かどうかわかりません)、私は自分でそれを選択することを好みます。

51
user955249

どちらの方法でもこれをコーディングできます。

  1. アトミックとポーリングループの使用。
  2. condition_variableを使用します。

以下に両方の方法でコーディングしました。私のシステムでは、特定のプロセスが使用しているCPUの量をリアルタイムで監視できます。

まず、ポーリングループで:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

std::atomic<bool> is_ready(false);

void
test()
{
    std::this_thread::sleep_for(std::chrono::seconds(30));
    is_ready.store(true);
}

int
main()
{
    std::thread t(test);
    while (!is_ready.load())
        std::this_thread::yield();
    t.join();
}

私にとってこれは実行に30秒かかり、プロセスの実行中にCPUの約99.6%かかります。

または、condition_variable

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

bool is_ready(false);
std::mutex m;
std::condition_variable cv;

void
test()
{
    std::this_thread::sleep_for(std::chrono::seconds(30));
    std::unique_lock<std::mutex> lk(m);
    is_ready = true;
    cv.notify_one();
}

int
main()
{
    std::thread t(test);
    std::unique_lock<std::mutex> lk(m);
    while (!is_ready)
    {
        cv.wait(lk);
        if (!is_ready)
            std::cout << "Spurious wake up!\n";
    }
    t.join();
}

これは、30秒の実行中にプロセスが0.0%CPUを使用することを除いて、まったく同じ動作をします。バッテリー駆動のデバイスで実行するアプリを作成している場合、後者はバッテリーでほぼ無限に簡単です。

確かに、std::condition_variableの実装が非常に貧弱であれば、ポーリングループと同じ非効率になる可能性があります。しかし実際には、そのようなベンダーはかなり早く廃業すべきです。

更新

笑顔のために、偽のウェイクアップ検出器を使用して、condition_variable待機ループを拡張しました。もう一度実行しましたが、何も出力されませんでした。 1つの偽のウェイクアップではありません。それはもちろん保証されません。しかし、それは高品質の実装が達成できることを示しています。

83
Howard Hinnant

_std::condition_variable_の目的は、ある条件が真になるのを待つことです。これは、not通知の単なる受信者になるように設計されています。たとえば、コンシューマスレッドがキューが空でなくなるまで待機する必要がある場合に使用します。

_T get_from_queue() {
   std::unique_lock l(the_mutex);
   while (the_queue.empty()) {
     the_condition_variable.wait(l);
   }
   // the above loop is _exactly_ equivalent to the_condition_variable.wait(l, [&the_queue](){ return !the_queue.empty(); }
   // now we have the mutex and the invariant (that the_queue be non-empty) is true
   T retval = the_queue.top();
   the_queue.pop();
   return retval;
}

put_in_queue(T& v) {
  std::unique_lock l(the_mutex);
  the_queue.Push(v);
  the_condition_variable.notify_one();  // the queue is non-empty now, so wake up one of the blocked consumers (if there is one) so they can retest.
}
_

コンシューマ(_get_from_queue_)はnot条件変数を待機しています。条件the_queue.empty()を待機しています。条件変数は、待機中にそれらをスリープ状態にする方法を提供し、同時にミューテックスを解放し、ウェイクアップを逃す競合状態を回避する方法でそれを行います。

待機している条件は、ミューテックス(条件変数で待機するときに解放するミューテックス)で保護する必要があります。これは、条件がatomicである必要がほとんどないことを意味します。常にミューテックス内からアクセスします。

30
Wandering Logic