C++ 通知条件变量后使用互斥
通知条件变量在收到通知后重新锁定互斥锁的原因是什么 如果unique_lock未限定作用域或互斥锁未明确解锁,则以下代码段会死锁C++ 通知条件变量后使用互斥,c++,mutex,C++,Mutex,通知条件变量在收到通知后重新锁定互斥锁的原因是什么 如果unique_lock未限定作用域或互斥锁未明确解锁,则以下代码段会死锁 #include <future> #include <mutex> #include <iostream> using namespace std; int main() { std::mutex mtx; std::condition_variable cv; //simulate another
#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;
}
#包括
#包括
#包括
使用名称空间std;
int main()
{
std::互斥mtx;
std::条件变量cv;
//模拟另一个工作线程发送通知
自动as=std::async([&cv](){std::this_线程::sleep_for(std::chrono::seconds(2));
cv.notify_all();});
//取消注释范围(或下面的解锁)以防止死锁
//{
标准:唯一锁lk(mtx);
//本短样本中未涉及虚假唤醒预防
//除非这是再次锁定的答案/理由的一部分
cv.等待(lk);
//}
std::cout您尝试锁定互斥锁两次。一次是使用唯一的_锁,另一次是使用显式的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.
std::unique_lock lk(mtx);//此选项锁定unique_lock对象的生存期
cv.wait(lk);//这将在等待时解锁,但在返回时重新锁定
std::cout您尝试锁定互斥锁两次。一次是使用唯一的_锁,另一次是使用显式的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.
std::unique_lock lk(mtx);//此选项锁定unique_lock对象的生存期
cv.wait(lk);//这将在等待时解锁,但在返回时重新锁定
std::cout因为条件wait可能返回的原因除了被通知(如信号)之外,或者仅仅是因为其他人写入了相同的64字节缓存线。或者它可能已经被通知,但条件不再为真,因为另一个线程处理了它
因此,互斥锁被锁定,这样代码就可以在保持互斥锁的同时检查其条件变量。也许这只是一个布尔值,表示它已经准备就绪
不要跳过这一部分。如果你跳过了,你会后悔的。因为等待条件可能返回的原因除了被通知之外,比如信号,或者仅仅是因为其他人写入了相同的64字节缓存线。或者它可能已经被通知,但条件不再是真的,因为另一个线程处理了它
因此,互斥锁被锁定,这样代码就可以在保持互斥锁的同时检查其条件变量。也许这只是一个布尔值,表示它已经准备就绪
不要跳过这一部分。如果你跳过了,你会后悔的。让我们暂时想象一下,从等待返回时,互斥锁未锁定:
线程1:
锁定互斥锁,检查谓词(无论是什么),并在发现谓词的形式不可接受时,等待其他某个线程将其置于可接受的形式。该等待会自动将线程1置于睡眠状态,并解锁互斥体。解锁互斥体后,其他某个线程将有权将谓词置于可接受状态(谓词自然不是线程安全的)
线程2:
同时,此线程正在尝试锁定互斥体
,并将谓词置于线程1可以接受的状态,以使其继续通过等待。它必须在互斥体
锁定的情况下执行此操作。互斥体
防止谓词一次被多个线程访问(读或写)
一旦线程2将互斥体
置于可接受的状态,它将通知条件变量
,并解锁互斥体
(这两个操作的顺序与此参数无关)
线程1:
现在线程1已经收到通知,我们假设互斥锁在从等待返回时未被锁定。线程1必须做的第一件事是检查谓词,看看它是否确实可以接受(这可能是一个虚假的唤醒)。但它不应在未锁定互斥体的情况下检查谓词。否则,其他线程可能会在该线程检查谓词后立即更改该谓词,从而使该检查的结果无效
因此,这个线程在唤醒时必须做的第一件事就是锁定互斥锁,然后检查谓词
因此,在从wait
返回时锁定mutex
确实更方便。否则等待的线程将不得不在100%的时间内手动锁定它
让我们再看看线程1进入等待时发生的事件:我说过睡眠和解锁是原子发生的。这非常重要。想象一下,如果线程1必须手动解锁互斥锁,然后调用等待:在这个假设场景中,线程1可以解锁互斥锁,然后当另一个线程获得互斥体时被中断,更改谓词,解锁互斥体,并向条件变量发出信号,所有这些都是在线程1调用等待之前发生的。现在线程1将永远休眠,因为没有线程会看到谓词需要更改,并且条件变量需要更改代码>需要信号
因此,unlock
/enter-wait
必须以原子方式进行。如果lock
/exit-wait
也以原子方式进行,则该API更易于使用。让我们暂时设想一下,从wait
返回时,互斥锁未被锁定:
线程1:
锁定mutex
,检查谓词(无论是什么),发现谓词的形式不可接受时,等待其他线程将其置于可接受的形式。等待会自动将线程1置于睡眠状态,并使用m解锁mutex