通知条件变量后使用互斥锁
Use of mutex after condition variable has been notified
通知的条件变量在收到通知后重新锁定互斥锁的原因是什么?
以下代码段死锁(如果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 notifiedn" << std::flush;
//uncomment unlock (or scoping above) to prevent deadlock
//mtx.unlock();
mtx.lock();
//do something
mtx.unlock();
std::cout << "End may never be reachedn" << std::flush;
return 0;
}
即使重新阅读一些文档和示例,我仍然没有发现这一点很明显。
可以通过网络找到的大多数示例都是具有固有unique_lock范围的小型代码示例。
我们是否应该使用不同的互斥锁来处理关键部分(互斥锁 1(和条件变量等待并通知(互斥锁 2(?
注意:调试显示,在等待阶段结束后,"内部"互斥计数"(我认为结构__pthread_mutex_s的字段__count(从 1 变为 2。解锁后返回 0
您正在尝试锁定互斥锁两次。 一次使用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 notifiedn" << 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 notifiedn" << std::flush;
//do something - while still under the lock
return 0;
}
因为条件等待可能出于通知(如信号(之外的原因返回,或者只是因为其他人写入了相同的 64 字节缓存行。或者它可能已被通知,但条件不再为 true,因为另一个线程处理了它。
因此,互斥锁是锁定的,以便您的代码可以在持有互斥锁的同时检查其条件变量。也许这只是一个布尔值,表示它已准备就绪。
不要跳过该部分。如果你这样做,你会后悔的。
让我们暂时想象mutex
在从wait
返回时没有被锁定:
线程 1:
锁定mutex
,检查谓词(无论可能是什么(,并在发现谓词不是可接受的形式时,等待其他线程将其置于可接受的形式。 等待以原子方式使线程 1 进入睡眠状态并解锁mutex
。 解锁mutex
后,其他线程将有权将谓词置于可接受的状态(谓词本身不是线程安全的(。
线程 2:
同时,此线程正在尝试锁定mutex
并将谓词置于线程 1 可接受的状态,以继续等待。 它必须在锁定mutex
的情况下执行此操作。mutex
保护谓词不被多个线程一次访问(读取或写入(。
一旦线程 2 将mutex
置于可接受的状态,它就会通知condition_variable
并解锁mutex
(这两个操作的顺序与此参数无关(。
线程 1:
现在线程 1 已被通知,我们假设假设mutex
在从wait
返回时未被锁定。 线程 1 要做的第一件事是检查谓词以查看它是否真的可以接受(这可能是一个虚假的唤醒(。 但它不应该在没有锁定mutex
的情况下检查谓词。 否则,其他线程可能会在此线程检查谓词后立即更改谓词,从而使该检查的结果无效。
所以这个线程在唤醒时要做的第一件事就是锁定mutex
,然后检查谓词。
因此,从wait
返回时锁定mutex
实际上更方便。 否则,等待线程将不得不在 100% 的时间内手动锁定它。
让我们再看一下线程 1 进入wait
的事件:我说过睡眠和解锁是原子发生的。 这一点非常重要。 想象一下,如果线程 1 必须手动解锁mutex
然后调用wait
:在这个假设的场景中,线程 1 可以解锁mutex
,然后在另一个线程获取mutex
、更改谓词、解锁mutex
并发出condition_variable
信号时被中断,所有这些都在线程 1 调用wait
之前。 现在线程 1 永远休眠,因为没有线程会看到谓词需要更改,而condition_variable
需要信号。
因此,unlock
/enter-wait
必须以原子方式发生。 如果lock
/exit-wait
也是原子发生的,它会使 API 更易于使用。
- 基于模板值的条件变量
- 没有超时的C++条件变量
- 在条件变量中触发错误信号的频率是多少
- 在通知提升间处理条件变量时未按住锁会导致问题
- 通知条件变量后使用互斥锁
- 滥用条件变量
- 升压插值条件变量可以虚假唤醒吗?
- 子线程中的条件变量等待停止主线程中的执行
- 条件变量基本示例
- 正在连接的等待条件变量的线程会发生什么情况?
- C++11如何在1个线程中使用条件变量处理2个线程安全队列
- 当线程处理不同的类时,应该在哪里声明条件变量、互斥对象
- 为什么在同一条件变量上使用多个互斥锁会使此代码崩溃?
- 条件变量:wait_for.gcc错误
- 如何"stop"正在等待条件变量的分离线程?
- 如何杀死被条件变量锁定的线程?
- 使用互斥锁和条件变量作为成员时如何修复"use of deleted function"?
- C++ 多个使用者线程卡在条件变量上
- POSIX 条件变量和互斥体"竞争"
- 将cpp_redis pub/sub与条件变量一起使用时出现问题