互斥锁应该覆盖condition_variable.notify_all吗?

tte*_*ple 3 c++ multithreading

我实现了一个类,该类使我可以将线程与条件变量进行同步。我发现有关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();
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

Yak*_*ont 5

如果互斥锁保持在状态测试更改状态和thr通知之间的某个时间间隔内,则没有释放锁的风险。

{
    std::lock_guard<std::mutex> lk(_address_mutex);
    _address = address;
}
_cv.notify_all();
Run Code Online (Sandbox Code Playgroud)

在这里,互斥锁在_address更改后已解锁。因此没有风险。

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

{
    std::lock_guard<std::mutex> lk(_address_mutex);
}
_address = address;
_cv.notify_all();
Run Code Online (Sandbox Code Playgroud)

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

_address = address;
{
    std::lock_guard<std::mutex> lk(_address_mutex);
}
_cv.notify_all();
Run Code Online (Sandbox Code Playgroud)

但是,以上内容再次变得正确(如果有点奇怪)。


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

waiting|signalling
lock
test
        test changed
        notification
listen+unlock
Run Code Online (Sandbox Code Playgroud)

以上是错过通知的示例。

只要在测试更改之后且在通知发生之前,我们可以在任何地方保存互斥锁。