简化的目标是强制在3个不同的线程中一个接一个地调用3个成员函数(线程A调用F :: first,线程B F :: second,线程C F :: third)。
为了获得执行线程的顺序,我使用了1个条件变量和2个布尔值来指示第一线程和第二线程是否完成了工作。
在代码中:
std::mutex mtx;
std::condition_variable cv;
bool firstPrinted = false;
bool secondPrinted = false;
class F {
public:
void first(std::function<void()> printFirst) {
std::unique_lock<std::mutex> lck(mtx);
std::cout << "first\n";
printFirst();
firstPrinted = true;
cv.notify_one();
}
void second(std::function<void()> printSecond) {
std::unique_lock<std::mutex> lck(mtx);
std::cout << "second\n";
cv.wait(lck, []() { return firstPrinted; });
printSecond();
secondPrinted = true;
cv.notify_one();
}
void third(std::function<void()> printThird) {
std::unique_lock<std::mutex> lck(mtx);
std::cout << "third\n";
cv.wait(lck, []() { return secondPrinted; });
printThird();
}
};
auto first = []() {
std::cout << "1";
};
auto second = []() {
std::cout << "2";
};
auto third = []() {
std::cout << "3";
};
F f;
std::thread A(&F::first, &f, first);
std::thread B(&F::second, &f, second);
std::thread C(&F::third, &f, third);
A.join(); B.join(); C.join();
现在让我们考虑这种情况:
线程A不会首先启动-无论第一个启动线程是B还是C,它们都阻塞(等待)直到得到通知(B阻塞直到A通知,C阻塞直到B通知)
当第一个启动线程为C时,将出现无限等待(或可能死锁!?),这总是产生以下输出:
third
second
first
...and stalling here
从理论上讲,这不会发生,因为在线程C中调用cv.wait会解锁互斥锁,从而使线程B能够运行,而线程B又会等待(因为条件未变为真),因此它也会解锁锁定的互斥锁线程A首先开始,最后应该进入关键部分并通知B。
什么是导致程序停止的调用路径?
我想念什么细微差别?
如果我在上述想法上有误,请纠正我。
答案 0 :(得分:4)
std::condition_variable::notify_one()
将唤醒一个线程,等待condition_variable
。如果有多个线程正在等待,则将选择一个线程。它将唤醒,重新获取其谓词的锁定检查。如果该谓词仍为false
,它将返回其等待状态,并且通知实际上丢失了。
当运行first
的线程最后执行时,这就是这里发生的情况。到达notify_one
时,将有两个线程在等待condition_variable
。如果它通知运行third
的线程,则该谓词仍将返回false
。该线程将唤醒,无法通过谓词测试,然后返回等待状态。您的进程现在没有正在运行的线程,并且已冻结。
解决方案是使用std::condition_variable::notify_all()
。此函数唤醒所有个正在等待的线程,这些线程将一次重新锁定mutex
并检查自己的谓词。