通知条件变量后使用互斥

通知的条件变量在收到通知后重新锁定互斥锁的原因是什么

如果unique_lock没有作用域或互斥体未明确解锁,则以下代码会死锁

#include <future>
#include <mutex>
#include <iostream>

using namespace std;

int main()
{
    std::mutex mtx;
    std::condition_variable cv;

    //simulate another working thread sending notification
    auto as = std::async([&cv](){   std::this_thread::sleep_for(std::chrono::seconds(2));
                                    cv.notify_all();});

    //uncomment scoping (or unlock below) to prevent deadlock 
    //{

    std::unique_lock<std::mutex> lk(mtx);

    //Spurious Wake-Up Prevention not adressed in this short sample
    //UNLESS it is part of the answer / reason to lock again
    cv.wait(lk);

    //}

    std::cout << "CV notified\n" << std::flush;

    //uncomment unlock (or scoping  above) to prevent deadlock 
    //mtx.unlock();

    mtx.lock();
    //do something
    mtx.unlock();

    std::cout << "End may never be reached\n" << std::flush;

    return 0;
}

即使重新阅读some documentation and examples,我仍然看不到这一点。

在网络上可以找到的大多数示例是具有固有锁唯一范围的小型代码示例。

我们应该使用不同的互斥量来处理关键部分(互斥量1),而条件变量等待并通知(互斥量2)吗?

注意:调试显示,在等待阶段结束之后,“内部”“互斥计数”(我认为字段__count的结构__pthread_mutex_s)从1变为2。解锁后返回0。

wangyande108 回答:通知条件变量后使用互斥

您尝试两次锁定互斥锁。一次使用unique_lock,一次通过显式mutex.lock()调用。对于非递归互斥锁,它将在尝试重新锁定时死锁,从而使您知道自己有错误。

std::unique_lock<std::mutex> lk(mtx);   // This locks for the lifetime of the unique_lock object

cv.wait(lk);  // this will unlock while waiting,but relock on return

std::cout << "CV notified\n" << std::flush;

mtx.lock();  // This attempts to lock the mutex again,but will deadlock since unique_lock has already invoked mutex.lock() in its constructor.

该修复程序非常接近您未注释那些大括号的情况。只要确保您一次仅在互斥锁上激活一个锁即可。

此外,正如您所拥有的,您的代码易于被虚假唤醒。这是您的一些调整。您应该始终处于等待循环中,直到实际发生条件或状态(通常由互斥体自身保护)为止。对于简单的通知,布尔会这样做。

int main()
{
    std::mutex mtx;
    std::condition_variable cv;
    bool conditon = false;

    //simulate another working thread sending notification
    auto as = std::async([&cv,&mtx,&condition](){   
                                    std::this_thread::sleep_for(std::chrono::seconds(2));
                                    mtx.lock();
                                    condition = true;
                                    mtx.unlock();
                                    cv.notify_all();});

    std::unique_lock<std::mutex> lk(mtx); // acquire the mutex lock
    while (!condition)
    {
        cv.wait(lk);
    }

    std::cout << "CV notified\n" << std::flush;

    //do something - while still under the lock

    return 0;
}
,

因为条件等待可能会由于通知(例如信号)或其他人在同一64字节高速缓存行上写入而导致返回。或者它可能已经被通知,但条件不再成立,因为另一个线程处理了它。

因此互斥锁被锁定,以便您的代码可以在按住互斥锁的同时检查其条件变量。也许这只是一个布尔值,表示它已经准备好了。

不要不要跳过该部分。如果这样做,您会后悔的。

,

让我们暂时想象mutex返回时,wait没有被锁定:

线程1:

锁定mutex,检查谓词(无论可能是什么),并在发现谓词不处于可接受的形式后,等待其他线程将其置于可接受的形式。 原子上的等待使线程1进入睡眠状态并解锁mutex。将mutex解锁后,其他一些线程将具有将谓词置于可接受状态的权限(该谓词自然不是线程安全的)。

线程2:

同时,该线程试图锁定mutex并将谓词置于线程1可以继续等待之前的可接受状态。它必须在锁定mutex的情况下执行此操作。 mutex保护谓词免于一次被多个线程访问(读或写)。

一旦线程2将mutex置于可接受的状态,它将通知condition_variable并解锁mutex(这两个动作的顺序与此参数无关)。 / p>

线程1:

现在线程1已被通知,并且我们假设mutex在从wait返回时未锁定。线程1要做的第一件事是检查谓词,看它是否确实可以接受(这可能是虚假的唤醒)。但是,如果mutex未锁定,则不应检查谓词。否则,其他线程可能会在此线程检查谓词后立即更改谓词,从而使该检查结果无效。

因此,该线程在唤醒时要做的非常第一件事是锁定mutex,然后然后检查谓词。

因此,mutexwait返回时被锁定确实是更方便的方法。否则,等待线程将不得不100%地手动锁定它。


让我们再次看一下线程1进入wait时的事件:我说睡眠和解锁发生在原子上。这个非常重要。想象一下,如果线程1必须手动解锁mutex then 调用wait:在这种情况下,线程1可以解锁mutex,然后被中断而另一个线程获取mutex,更改谓词,解锁mutex并发信号通知condition_variable,所有这些都在线程1调用wait之前进行。现在,线程1永远处于睡眠状态,因为没有线程会看到谓词需要更改,而condition_variable需要发出信号。

因此,unlock / enter-wait在原子上发生是必要的。如果lock / exit-wait也是原子发生的,它将使API易于使用。

本文链接:https://www.f2er.com/2721259.html

大家都在问