如果condition_variable是一个类的成员,我的理解是:
根据这些预期,我的问题是:下面的示例代码为什么不通知等待线程?
#include <mutex>
#include <condition_variable>
#define NOTIFY_IN_DESTRUCTOR
struct notify_on_delete {
std::condition_variable cv;
~notify_on_delete() {
#ifdef NOTIFY_IN_DESTRUCTOR
cv.notify_all();
#endif
}
};
int main () {
for (int trial = 0; trial < 10000; ++trial) {
notify_on_delete* nod = new notify_on_delete();
std::mutex flag;
bool kill = false;
std::thread run([nod, &flag, &kill] () {
std::unique_lock<std::mutex> lock(flag);
kill = true;
nod->cv.wait(lock);
});
while(true) {
std::unique_lock<std::mutex> lock(flag);
if (!kill) continue;
#ifdef NOTIFY_IN_DESTRUCTOR
delete nod;
#else
nod->cv.notify_all();
#endif
break;
}
run.join();
#ifndef NOTIFY_IN_DESTRUCTOR
delete nod;
#endif
}
return 0;
}在上面的代码中,如果没有定义NOTIFY_IN_DESTRUCTOR,那么测试将可靠地运行到完成。然而,当定义NOTIFY_IN_DESTRUCTOR时,测试将随机挂起(通常在几千次试验之后)。
我正在使用Apple : Apple版本9.0.0 (clang-900.0.39.2)进行编译,目标: x86_64-apple-darwin17.3.0线程模型: posix指定的C++14,使用设置的调试标志进行编译。
编辑:
为了澄清:这个问题是关于condition_variable实例的指定行为的语义。上面的第二点似乎在下面的报价中被重新执行
封锁要求:不会有线程阻塞*这。 注意事项:也就是说,所有线程都应已被通知;它们随后可能会阻塞等待中指定的锁。这就放松了通常的规则,这就要求所有的等待呼叫发生在毁灭之前。只有解除阻塞的通知才需要在销毁之前发生。用户应该注意确保在析构函数启动后没有线程等待*这一点,特别是当等待线程在循环中调用等待函数或使用接受谓词的wait、wait_for或wait_until的重载时。- end注意事项
核心的语义问题似乎是“阻止”的意思。我现在对上述引文的解释是,在行之后
cv.notify_all(); // defined NOTIFY_IN_DESTRUCTOR在~notify_on_delete()中,线程测试没有在nod上被“阻塞”--也就是说,我目前理解在调用“取消阻塞等待的通知”之后发生了,因此根据引号,已经满足了继续销毁condition_variable实例的要求。
有人能澄清“阻塞的”或“取消阻塞的通知”,大意是在上面的代码中,对notify_all()的调用不符合~condition_variable()的要求吗?
发布于 2018-01-13 17:39:47
我很确定您的供应商的实现是坏的。从遵从cv/mutex类契约的角度来看,您的程序看起来几乎没有问题。我无法100%的核实,我是背后的一个版本。
在condition_variable (CV)类中,“阻塞”的概念令人困惑,因为有许多事情要阻止。契约要求实现比pthread_cond*上的单板更复杂(例如)。我对它的解读表明,一份简历至少需要2个p线程_cond_t才能实现。
关键是在线程等待CV时,析构函数有一个定义;它的破坏是在CV.wait和~CV之间的竞争中。朴素的实现只需~CV广播condvar,然后消除它,并让CV.wait记住局部变量中的锁,这样当它从阻塞它的运行时概念中醒来时,就不必再引用对象了。在这个实施中,~CV变成了一种“火与忘”的机制。
可悲的是,一个赛车CV.wait可以满足先决条件,但还没有完成与对象的交互,当~CV潜入并摧毁它。要解决种族CV.wait和~CV需要相互排斥,因此CV至少需要一个私有互斥体来解析种族。
我们还没完成呢。通常没有潜在的支持。内核用于“等待由锁控制的cv,并在我被阻止后释放另一个锁”。我认为即使是posix的人也觉得这太有趣了,不需要。因此,在我的简历中隐藏互斥是不够的,我实际上需要一种允许我在其中处理事件的机制;因此,在CV的实现中需要一个私有的condvar。强制性戴维·帕纳斯模因。
几乎可以,因为正如Marek所指出的,在类的销毁开始之后,您依赖于引用一个类;而不是cv/mutex类,您的notify_on_delete类。这场冲突有点学术性。我怀疑clang会依赖于nod在控制权转移到nod->cv.Waet()之后是否仍然有效;但是大多数编译器供应商的真正客户是基准,而不是程序员。
作为一般的注意事项,多线程编程是困难的,现在已经在c++线程模型上达到顶峰,最好给它十年或二十年的时间来稳定下来。它的合同是惊人的。当我第一次看你的程序时,我想‘嗯,你不可能摧毁一个因为RAII而可以访问的cv’。我真傻。
线程是另一个可怕的线程API。至少它没有尝试过火,而且已经足够成熟,以至于强大的测试套件使供应商保持一致。
发布于 2018-01-04 11:18:19
定义NOTIFY_IN_DESTRUCTOR时:
调用notify_one()/notify_all()并不意味着等待线程立即被唤醒,当前线程将等待另一个线程。这只是意味着,如果等待线程在当前线程调用通知之后的某个时刻醒来,它应该继续进行。因此,本质上,您可能是在等待线程唤醒之前删除条件变量(取决于线程的调度方式)。
对于为什么挂起的解释,即使在其他线程等待时删除了条件变量,也取决于等待/通知操作是使用与条件变量关联的队列实现的。这些队列保存等待条件变量的线程。释放条件变量将意味着摆脱这些线程队列。
https://stackoverflow.com/questions/48093715
复制相似问题