无限等待条件变量
Infinite waiting on condition variable
简化的目标是强制在3个不同的线程中一个一个调用3个成员函数(线程A调用F::first,线程BF::second,线程CF::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 进入 运行 的互斥量,而后者又会等待(因为条件未变为真),因此它解锁锁定的互斥锁,并允许线程 A 首先启动,最终应该进入临界区并通知 B。
导致程序卡顿的调用路径是什么?
我错过了什么细微差别?
以上思路如有错误请指正
std::condition_variable::notify_one()
将唤醒等待 condition_variable
的 一个 线程。如果有多个线程在等待,则会选择一个。它会醒来,重新获取锁检查它的谓词。如果该谓词仍然是 false
,它将 return 进入等待状态并且通知实际上丢失了。
这就是线程 运行 first
最后执行时发生的情况。当它到达 notify_one
时,将有两个线程等待 condition_variable
。如果它通知线程 运行 third
,它的谓词仍将是 return false
。该线程将唤醒,它的谓词测试失败并且 return 等待。您的进程现在没有 运行 个线程并且被冻结。
解决方法是使用std::condition_variable::notify_all()
。此函数唤醒 所有 等待线程,这些线程将一次一个地重新锁定 mutex
并检查它们自己的谓词。
简化的目标是强制在3个不同的线程中一个一个调用3个成员函数(线程A调用F::first,线程BF::second,线程CF::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 进入 运行 的互斥量,而后者又会等待(因为条件未变为真),因此它解锁锁定的互斥锁,并允许线程 A 首先启动,最终应该进入临界区并通知 B。
导致程序卡顿的调用路径是什么?
我错过了什么细微差别?
以上思路如有错误请指正
std::condition_variable::notify_one()
将唤醒等待 condition_variable
的 一个 线程。如果有多个线程在等待,则会选择一个。它会醒来,重新获取锁检查它的谓词。如果该谓词仍然是 false
,它将 return 进入等待状态并且通知实际上丢失了。
这就是线程 运行 first
最后执行时发生的情况。当它到达 notify_one
时,将有两个线程等待 condition_variable
。如果它通知线程 运行 third
,它的谓词仍将是 return false
。该线程将唤醒,它的谓词测试失败并且 return 等待。您的进程现在没有 运行 个线程并且被冻结。
解决方法是使用std::condition_variable::notify_all()
。此函数唤醒 所有 等待线程,这些线程将一次一个地重新锁定 mutex
并检查它们自己的谓词。