97

の使い方にちょっと戸惑いますstd::condition_variable。を呼び出す前にを作成unique_lockする必要があることを理解しています。私が見つけられないのは、またはを呼び出す前に一意のロックも取得する必要があるかどうかです。mutexcondition_variable.wait()notify_one()notify_all()

cppreference.comの例は矛盾しています。たとえば、notify_one ページには次の例が示されています。

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

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    cv.notify_one();

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) {
        lk.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
        std::cerr << "Notifying again...\n";
        cv.notify_one();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); t2.join();
}

ここでは、最初のロックは取得されませんがnotify_one()、2 番目のロックは取得されnotify_one()ます。例のある他のページを見ると、ほとんどがロックを取得していないさまざまなことがわかります。

  • を呼び出す前にミューテックスをロックすることを自分で選択できますnotify_one()か? また、なぜロックすることを選択するのでしょうか?
  • 与えられた例では、なぜ最初の にはロックがなくnotify_one()、その後の呼び出しにはあるのですか。この例は間違っていますか、それとも何らかの理由がありますか?
4

6 に答える 6

11

他の人が指摘しているようにnotify_one()、競合状態やスレッド関連の問題に関して、 を呼び出すときにロックを保持する必要はありません。ただし、場合によっては、が呼び出されるcondition_variable前に が破棄されるのを防ぐために、ロックを保持する必要がある場合があります。notify_one()次の例を検討してください。

thread t;

void foo() {
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    t = std::thread([&]() {
        {
            std::lock_guard<std::mutex> l(m);  // (1)
            done = true;  // (2)
        }  // (3)
        cv.notify_one();  // (4)
    });  // (5)

    std::unique_lock<std::mutex> lock(m);  // (6)
    cv.wait(lock, [&done]() { return done; });  // (7)
}

void main() {
    foo();  // (8)
    t.join();  // (9)
}

tスレッドを作成した後、条件変数 ((5) と (6) の間のどこか) の待機を開始する前に、新しく作成されたスレッドへのコンテキスト スイッチがあるとします。スレッドtは、ロックを取得し (1)、述語変数を設定し (2)、ロックを解放します (3)。notify_one()(4) が実行される前に、この時点で別のコンテキスト スイッチがあるとします。メイン スレッドはロックを取得し (6)、行 (7) を実行します。この時点で述語が戻りtrue、待機する理由がないため、ロックを解放して続行します。fooは (8) を返し、そのスコープ内の変数 ( を含むcv) は破棄されます。スレッドがメイン スレッドに参加する前にt(9)、実行を終了する必要があるため、実行を中断したところから続行します。cv.notify_one()(4)、その時点cvですでに破壊されています!

この場合の可能な修正は、呼び出し時にロックを保持し続けることですnotify_one(つまり、行 (3) で終わるスコープを削除します)。そうすることで、前のスレッドt呼び出しが新しく設定された述語変数をチェックして続行できるようにします。これ は、チェックを行うために現在保持されているロックを取得する必要があるためです。そのため、リターン後にスレッドによってアクセスされないようにします。notify_onecv.waittcvtfoo

要約すると、この特定のケースの問題は実際にはスレッドに関するものではなく、参照によってキャプチャされた変数の有効期間に関するものです。cvthread を介して参照によってキャプチャされるため、スレッドの実行中に存続tすることを確認する必要があります。cvここに示す他の例ではcondition_variablemutexオブジェクトがグローバル スコープで定義されているため、この問題は発生しません。したがって、プログラムが終了するまで存続することが保証されます。

于 2018-10-23T13:35:39.423 に答える
10

状況

vc10 と Boost 1.56 を使用して、このブログ投稿が示唆するように、同時キューを実装しました。作成者は、競合を最小限に抑えるためにミューテックスのロックを解除します。つまり、notify_one()ミューテックスがロック解除された状態で呼び出されます。

void push(const T& item)
{
  std::unique_lock<std::mutex> mlock(mutex_);
  queue_.push(item);
  mlock.unlock();     // unlock before notificiation to minimize mutex contention
  cond_.notify_one(); // notify one waiting thread
}

ミューテックスのロック解除は、 Boost ドキュメントの例に裏付けられています。

void prepare_data_for_processing()
{
    retrieve_data();
    prepare_data();
    {
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

問題

それでも、これは次の不安定な動作につながりました。

  • whilenotify_one()まだ呼び出されていませcond_.wait()boost::thread::interrupt()
  • 一度デッドロックnotify_one()が初めて呼び出されました。またはそれ以上cond_.wait()待機を終了することはできません。boost::thread::interrupt()boost::condition_variable::notify_*()

解決

行を削除するとmlock.unlock()、コードが期待どおりに機能するようになりました (通知と割り込みによって待機が終了します)。notify_one()ミューテックスがまだロックされている状態で呼び出され、スコープを離れるとすぐにロックが解除されることに注意してください。

void push(const T& item)
{
  std::lock_guard<std::mutex> mlock(mutex_);
  queue_.push(item);
  cond_.notify_one(); // notify one waiting thread
}

つまり、少なくとも私の特定のスレッド実装では、を呼び出す前にミューテックスをロック解除してはいけませんがboost::condition_variable::notify_one()、どちらの方法も正しいようです。

于 2015-06-18T13:58:05.100 に答える
0

場合によっては、cv が他のスレッドによって占有 (ロック) されている可能性があります。notify_*() の前に、ロックを取得して解放する必要があります。
そうでない場合、notify_*() はまったく実行されない可能性があります。

于 2018-03-05T12:34:58.570 に答える