首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >销毁条件变量随机丢失通知

销毁条件变量随机丢失通知
EN

Stack Overflow用户
提问于 2018-01-04 10:55:00
回答 2查看 1.4K关注 0票数 17

如果condition_variable是一个类的成员,我的理解是:

  1. 条件变量在类析构函数完成后被销毁。
  2. 条件变量的销毁不需要等待收到通知。

根据这些预期,我的问题是:下面的示例代码为什么不通知等待线程?

代码语言:javascript
复制
#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注意事项 

核心的语义问题似乎是“阻止”的意思。我现在对上述引文的解释是,在行之后

代码语言:javascript
复制
cv.notify_all(); // defined NOTIFY_IN_DESTRUCTOR

在~notify_on_delete()中,线程测试没有在nod上被“阻塞”--也就是说,我目前理解在调用“取消阻塞等待的通知”之后发生了,因此根据引号,已经满足了继续销毁condition_variable实例的要求。

有人能澄清“阻塞的”或“取消阻塞的通知”,大意是在上面的代码中,对notify_all()的调用不符合~condition_variable()的要求吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 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。至少它没有尝试过火,而且已经足够成熟,以至于强大的测试套件使供应商保持一致。

票数 5
EN

Stack Overflow用户

发布于 2018-01-04 11:18:19

定义NOTIFY_IN_DESTRUCTOR时:

调用notify_one()/notify_all()并不意味着等待线程立即被唤醒,当前线程将等待另一个线程。这只是意味着,如果等待线程在当前线程调用通知之后的某个时刻醒来,它应该继续进行。因此,本质上,您可能是在等待线程唤醒之前删除条件变量(取决于线程的调度方式)。

对于为什么挂起的解释,即使在其他线程等待时删除了条件变量,也取决于等待/通知操作是使用与条件变量关联的队列实现的。这些队列保存等待条件变量的线程。释放条件变量将意味着摆脱这些线程队列。

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48093715

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档