Ang*_*iel 15 c++ multithreading c++11
鉴于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;
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,如果未定义NOTIFY_IN_DESTRUCTOR,则测试将可靠地运行至完成.但是,当定义NOTIFY_IN_DESTRUCTOR时,测试将随机挂起(通常在几千次试验后).
我正在使用Apple Clang进行编译:Apple LLVM版本9.0.0(clang-900.0.39.2)目标:x86_64-apple-darwin17.3.0线程模型:指定了posix C++ 14,使用设置的DEBUG标志进行编译.
编辑:
澄清一下:这个问题是关于condition_variable实例的指定行为的语义.上面的第二点似乎在以下引文中得到了改进:
Blockquote要求:*此上没有线程被阻止.[注意:也就是说,所有线程都应该被通知; 他们可能随后阻止等待中指定的锁定.这放宽了通常的规则,这将要求所有等待呼叫在破坏之前发生.只有取消阻止等待的通知才能在销毁之前发生.一旦析构函数启动,用户应该注意确保没有线程等待*,特别是当等待的线程在循环中调用wait函数或使用带谓词的wait,wait_for或wait_until的重载时. - 结束说明]
核心语义问题似乎是"阻止"的意思.我现在对上面引用的解释是在行之后
cv.notify_all(); // defined NOTIFY_IN_DESTRUCTOR
Run Code Online (Sandbox Code Playgroud)
在〜notify_on_delete()线程测试是不是 "阻塞"点头-这是说,我目前了解,在此调用后"的通知解除等待" 已经发生,所以要根据要求得到满足的报价继续销毁condition_variable实例.
有人可以提供"阻止"或"通知解除阻止"的说明,即在上面的代码中,对notify_all()的调用不满足~condition_variable()的要求吗?
当定义NOTIFY_IN_DESTRUCTOR时:
调用notify_one()/notify_all()并不意味着等待线程立即被唤醒并且当前线程将等待另一个线程.它只是意味着如果等待线程在当前线程调用notify之后的某个时刻唤醒,它应该继续.所以从本质上讲,您可能会在等待线程唤醒之前删除条件变量(取决于线程的调度方式).
解释为什么它挂起,即使在另一个线程正在等待它时删除条件变量取决于使用与条件变量相关联的队列实现等待/通知操作的事实.这些队列使线程等待条件变量.释放条件变量意味着摆脱这些线程队列.
我很确定您的供应商实施已损坏。从遵守 cv/mutex 类契约的角度来看,您的程序看起来几乎没问题。我无法\xe2\x80\x99t 100% 验证,我落后于一个版本。
\n\n\xe2\x80\x9cblocking\xe2\x80\x9d 的概念在 condition_variable (CV) 类中令人困惑,因为有多个事物需要阻塞。该合约要求实现比 pthread_cond* 上的饰面更复杂(例如)。我对它的阅读表明,单个 CV 至少需要 2 个 pthread_cond_t\xe2\x80\x99s 来实现。
\n\n关键是当线程等待 CV 时,析构函数有一个定义;它的毁灭是在 CV.wait 和 ~CV 之间的竞争中。天真的实现只是让 ~CV 广播 condvar 然后消除它,并让 CV.wait 记住局部变量中的锁,这样当它从阻塞的运行时概念中唤醒时,它就不再需要引用该对象。在该实现中,~CV 成为 \xe2\x80\x9cfire 并忘记\xe2\x80\x9d 机制。
\n\n遗憾的是,当 ~CV 潜入并摧毁它时,赛车 CV.wait 可以满足先决条件,但尚未完成与对象的交互。为了解决竞争,CV.wait 和 ~CV 需要相互排除,因此 CV 至少需要一个私有互斥体来解决竞争。
\n\n我们还没有完成\xe2\x80\x99。通常没有\xe2\x80\x99t 底层支持[例如。kernel ] 对于像 \xe2\x80\x9cwait 这样的操作,在由锁控制的 cv 上等待,并在我被阻止\xe2\x80\x9d 后释放另一个锁。我认为即使是 posix 的人也觉得这太有趣了,不需要。因此,在我的简历中埋入互斥体是不够的,我实际上需要一种允许我处理其中的事件的机制;因此 CV 的实现中需要一个私有 condvar。 强制性大卫·帕纳斯模因。
\n\n几乎没问题,因为正如 Marek R 指出的那样,您依赖于在类的销毁开始后对其进行引用;不是 cv/mutex 类,而是您的 notification_on_delete 类。这种冲突有点学术性。我怀疑 clang 是否依赖于在控制权转移到 nod->cv.wait() 后 nod 仍然有效;但大多数编译器供应商的真正客户是基准测试,而不是程序员。
\n\n正如一般注意事项,多线程编程很困难,并且现在在 C++ 线程模型上达到了顶峰,最好给它一两年的时间来稳定下来。它的\xe2\x80\x99s合同是惊人的。当我第一次看你的程序时,我想 \xe2\x80\x98duh,你不可能销毁可以访问的 cv,因为 RAII\xe2\x80\x99。愚蠢的我。
\n\nPthreads 是另一个糟糕的线程 API。至少它不会尝试超越,并且足够成熟,强大的测试套件可以让供应商保持一致。
\n