condition_variable.notify_all 是不是应该被互斥锁覆盖?

Posted

技术标签:

【中文标题】condition_variable.notify_all 是不是应该被互斥锁覆盖?【英文标题】:Should condition_variable.notify_all be covered by mutex lock?condition_variable.notify_all 是否应该被互斥锁覆盖? 【发布时间】:2018-02-24 01:43:52 【问题描述】:

我已经实现了一个类,它允许我将线程与条件变量同步。关于 notify_all 应该在锁内还是在锁外完成,我发现了相互矛盾的信息。我发现了两种方式构建的示例。

先释放锁的参数是为了防止等待线程在被通知释放后立即阻塞在互斥体上。

反对首先释放锁的论点是断言等待线程可能会错过通知。

发布功能的两个版本在这里:

// version 1 - unlock then notify.
void release(int address = 1)

    
        std::lock_guard<std::mutex> lk(_address_mutex);
        _address = address;
    
    _cv.notify_all();


// version 2 - notify then unlock
void release(int address = 1)

    std::lock_guard<std::mutex> lk(_address_mutex);
    _address = address;
    _cv.notify_all();

作为参考,等待代码如下所示:

bool wait(const std::chrono::microseconds dur, int address = 1)

    std::unique_lock<std::mutex> lk(_address_mutex);
    if (_cv.wait_for(lk, dur, [&] return _address == address; ))
    
        _address = 0;
        return true;
    
    return false;

在版本 1 中是否存在等待线程丢失通知的风险,其中允许互斥锁在 notify_all 之前超出范围?如果是这样,它是如何发生的? (这对我来说是如何导致错过通知的并不明显。)

我可以清楚地看到在通知期间保持互斥锁如何导致等待线程立即进入等待状态。但是,如果它可以防止错过通知,这是一个很小的代价。

【问题讨论】:

理论上在调用 notify 之前释放锁更有效,但实际上大多数编译器优化了如果你不这样做可能发生的潜在低效率。 这里有一些有趣的问题:github.com/isocpp/CppCoreGuidelines/issues/925 两者都可以,见手册pthread_cond_broadcast,见类似问题***.com/questions/17101922/…直播时不用锁。 【参考方案1】:

没有释放锁的风险如果互斥锁在条件测试状态改变和thr通知之间的某个时间间隔内被持有。


    std::lock_guard<std::mutex> lk(_address_mutex);
    _address = address;

_cv.notify_all();

这里,在_address 更改后,互斥锁被解锁。所以没有风险。

如果我们将_address 修改为原子,天真地看起来是正确的:


    std::lock_guard<std::mutex> lk(_address_mutex);

_address = address;
_cv.notify_all();

但事实并非如此;在这里,互斥锁在修改条件测试和通知之间的整个期间都被释放,

_address = address;

    std::lock_guard<std::mutex> lk(_address_mutex);

_cv.notify_all();

然而,上面的内容再次变得正确(如果有点奇怪的话)。


风险在于条件测试将在互斥锁处于活动状态(为假)的情况下进行评估,然后更改,然后发送通知,然后等待线程等待通知并释放互斥锁。

waiting|signalling
lock
test
        test changed
        notification
listen+unlock

以上是错过通知的示例。

只要我们在测试更改之后和通知之前的任何地方都持有互斥锁,它就不会发生。

【讨论】:

我编辑了您的帖子以显示一些时间示例,看看我是否完全理解您在说什么。 @ttemple 那是错误的地方。无论如何,您似乎不明白在运行测试时互斥锁被锁定。然后在等待信号时自动解锁互斥锁(在另一个线程中这两者之间不会发生任何事情)。 那么在“测试条件”之前,互斥锁会锁定吗?条件不成立则释放? @ttemple 在运行 lambda 测试时互斥锁被锁定。如果失败,它会自动释放互斥体并等待信号。 cpp 参考很好地描述了 lambda 测试的工作原理。 当(如果)等待线程被释放时,它会自动重新锁定互斥锁,对吗?

以上是关于condition_variable.notify_all 是不是应该被互斥锁覆盖?的主要内容,如果未能解决你的问题,请参考以下文章

std::condition_variable 在 std::condition_variable::notify_all() 从其他线程后未正确唤醒

std::condition_variable::notify_all() - 我需要一个例子

condition_variable.notify_all 是不是应该被互斥锁覆盖?

vs2012 condition_variable notify_one 崩溃

在 condition_variable::notify_all() 之后或之前解锁互斥锁?

C++ std::condition_variable notify_one()与notify_all()的作用