std::condition_variable::notify_one() 不会唤醒等待线程

std::condition_variable::notify_one() does not wake up a waiting thread

本文关键字:唤醒等待 线程 one condition variable notify std      更新时间:2023-10-16

我有一个类似于以下内容的代码:

#include <mutex>
#include <condition_variable>
#include <future>
std::mutex mtx;
std::condition_variable cv;
std::future<void> worker;
void worker_thread() {
  {
    std::lock_guard<std::mutex> lg{ mtx };
    // do something 1
  }
  cv.notify_one();
  // do something 2
}
int main() {
  {
    std::unique_lock<std::mutex> lg{ mtx };
    worker = std::async(std::launch::async, worker_thread);      
    cv.wait(lg);
  }
  // do something 3
}

主线程没有继续// do something 3,我不明白为什么。我认为在主线程传递cv.wait(lg)后,应该从工作线程到达行cv.notify_one(),因此没有理由挂起。

工作线程负责

一些流数据处理,而主线程主要负责 GUI 事件处理。

// do something 1是关于应该在工作线程内完成的一些初始化。主线程应等待工作线程完成它。

// do something 2是工作线程的主要工作。

// do something 3是主线程的主要工作。

cv.notify_one()更改为cv.notify_all()没有帮助。

条件变量的这种用法是否正确?

我必须回溯我原来的答案,我为此向Junekey道歉。我误读了代码,并得出结论,存在竞争条件。我无法重现该问题。我们需要一个实际上永远阻止 cv.wait 的示例,以便弄清楚它为什么要这样做。然而,如果没有其他原因,代码是不正确的,只是因为它可能会在worker_thread调用 cv.notify 之前收到虚假通知并通过 cv.wait。这种情况很少发生,但确实会发生。

此代码或多或少是规范的:

#include <mutex>
#include <condition_variable>
#include <thread>
std::mutex mtx;
std::condition_variable cv;
bool worker_done = false;  // <<< puts the "condition" in condition_variable
void worker_thread() {
    // do something 1
    {
        std::lock_guard<std::mutex> lg{ mtx };
        worker_done = true;  // ... and whatever
    }
    cv.notify_one();
    // do something 2
}
int main() {
    std::thread workman(worker_thread);
    {
        std::unique_lock<std::mutex> lg{ mtx };
        while (!worker_done) {
            cv.wait(lg);
        }
    }
    // do something 3
    workman.join();
}

对不起。这个问题是完全错误的。我认为"通知应该在主线程开始等待后触发"的说法仍然是正确的,但挂起的根本原因是虚假的觉醒,正如 Jive Dadson 和 Erik Alapää 指出的那样。

我无法在没有优化选项的情况下编译代码是有原因的,所以我误解了挂起点,因为调试器指向的点不是很清楚。挂点不是线cv.wait(lg).它在// do something 3里面的某个地方.

我有一个标志,如果成功// do something 1则设置,而如果在// do something 1内抛出异常,则会清除该标志。在// do something 3中,该标志被选中,如果它指示失败,则主线程调用worker.get()来重新抛出异常。由于// do something 2是无限循环的一种形式,因此如果主线程被虚假唤醒,则主线程只会挂起cv,因此尚未设置标志。

现在它工作正常!谢谢大家。