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

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

我有如下代码:

#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.notify_one() 行应该在主线程通过 cv.wait(lg) 之后从工作线程到达,所以没有理由挂起。

工作线程负责一些流数据处理,而主线程主要负责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();
}

对不起。这个问题是完全错误的。 我认为 "notification should be fired after the main thread have begun to wait" 的说法仍然正确,但挂起的根本原因是 虚假唤醒,正如 Jive Dadson 和 Erik Alapää 指出的那样。

有一个原因我无法编译代码w/o优化选项,所以我误解了挂起点,因为调试器指向的点不是很清楚。挂起点不是行 cv.wait(lg)。它在 // do something 3.

里面的某个地方

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

现在可以正常使用了!谢谢大家。