admin管理员组文章数量:1599528
condition_variable(条件变量)
简介
互斥锁用于上锁,条件变量用于等待;
condition_variable类是一个同步原语,可以用来阻塞一个线程,或者同时阻塞多个线程,直到另外一个线程修改了条件(a shared variable: the condition)并且通知了condition_variable(notify).
修改条件,并通知
- 获取一把锁,通常使用std::lock_guard。
- 在锁锁定的状态下,修改条件。
- 调用notify_one 或者 notify_all,进行通知
// 示例代码
struct CondInfo {
std::mutex m;
std::condition_variable cv;
bool ready;
} condInfo;
// modify the condition and notify
void ThreadFunc1() {
{
std::lock_guard<std::mutex> lck(condInfo.m);
condInfo.ready = true;
}
condInfo.cv.notify_one();
}
tips: 就算condition(例子中的ready)是atomic,也一定要在锁(例子中的m)锁定下进行修改,用来保证正确的将变量的修改发布到等待线程。
等待
- 获取一把std::unique_lock<std::mutex>(锁定同一把锁,用于保护shared variable the condition)。
- 检查条件,防止已经更新和通知。
- 执行wait、wait_for、wait_until,wait操作会释放锁,并挂起当前线程。
- 当条件变量被notify、超时或者虚假唤醒发生时,当前线程被唤醒,并锁定锁,当前线程需要再次检查条件,如果是虚假唤醒需要再次调用wait操作。
struct CondInfo {
std::mutex m;
std::condition_variable cv;
bool ready;
} condInfo;
// 示例代码
void ThreadFunc2()
{
{
std::unique_lock<std::mutex> lck(condInfo.m);
while (!condInfo.ready) { // 2.检查条件;4.再次检查,如果条件未变化(虚假唤醒),再次投入wait
condInfo.cv.wait(lck); // 3.执行wait
}
// 或者使用
// condInfo.wait(lck, []{return condInfo.ready;]); // 等同上面操作
}
// balabala
}
虚假唤醒
结合查到的资料分为两大类:
1、没有收到notify_one、notify_all, wait操作返回。导致虚假唤醒。
2、收到notify_one或者notify_all,但是当上锁时(wait:解锁,挂起,被notify唤醒,然后会再上锁),条件已经不为真。
第一类:当wait时被信号中断返回等一些系统原因。
第二类:系统调度相关:
比如:存在一个共享队列(生产消费队列),并有下面几个线程
线程 | 作用 |
---|---|
消费线程1 | 获取锁,移除队列一个元素(队列为空),释放锁。轮询进行这样的操作 |
消费线程2 | 尝试移除队列一个元素:首先获取锁,判断队列是否为空,若为空,则调用wait操作进行阻塞等,直到得到notify。 |
生产线程3 | 获取锁,向队列插入一个元素,然后释放锁,进行notify。 |
考虑下面这种场景:线程2阻塞再wait,线程3执行notify,当线程2由于notify得到cpu的调度,再进行上锁的时候,线程1已经完成了移除元素的操作,那么此时对于线程2就属于一个虚假唤醒(因为此时的队列还是为空)。
生产者&&消费者例子
#include <condition_variable>
#include <thread>
#include <stdio.h>
#include <queue>
struct CondInfo {
std::mutex m;
std::condition_variable cv;
std::queue<int> q;
} condInfo;
void Consume()
{
while (1) {
{
std::unique_lock<std::mutex> lck(condInfo.m);
// 使用overlo的wait操作,等价于下面注释的while循环
condInfo.cv.wait(lck, [](&condInfo){return !condInfo.q.empty();});
//while (condInfo.q.empty()) {
// condInfo.cv.wait(lck);
//}
printf("Consume elem:%d\n", condInfo.q.front());
condInfo.q.pop();
}
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}
}
void Produce()
{
int i = 0;
while (1) {
{
std::lock_guard<std::mutex> lockGuard(condInfo.m);
condInfo.q.push(i);
printf("Produce elem:%d\n", i);
i++;
}
condInfo.cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
int main()
{
std::thread consumeThd(&Consume);
std::thread produceThd(&Produce);
consumeThd.join();
produceThd.join();
return 0;
}
unique_lock
主要看它的几个构造函数:
- unique_lock() noexcept
构造一个unique_lock(不使用关联的mutex) - unique_lock( unique_lock&& other ) noexcept
移动构造函数,使用other初始化本身(和other的mutex关联),other则不和任何mutex关联。 - explicit unique_lock( mutex_type& m ); // 上述条件变量的例子就是使用该构造函数
使用一个关联mutex进行构造,调用m.lock()给关联mutex上锁,如果当前线程已经拥有了这把锁(这把锁是递归锁时除外),则该行为为未定义。 - unique_lock( mutex_type& m, std::defer_lock_t t ) noexcept
使用一个关联的mutex进行构造,不调用m.lock()进行上锁。 - unique_lock( mutex_type& m, std::try_to_lock_t t )
使用一个关联的mutex进行构造,调用m.try_lock()进行尝试上锁。如果当前线程已经拥有了这把锁(这把锁是递归锁时除外),则该行为为未定义。 - unique_lock( mutex_type& m, std::adopt_lock_t t );
使用一个关联的mutex进行构造,m已经被当前线程上锁
tips:unique_lock符合RAII,当超过作用域时,会进行析构(对持有的mutex进行解锁)
std::mutex m;
{
std::unique_lock<std::mutex> lck(m); // 使用第3个构造函数,对m进行上锁
// balabala
} // 超出作用域后,lck进行析构,对m进行解锁
本文标签: 进程通信conditionvariableampuniquelock
版权声明:本文标题:进程间通信:condition_variable && unique_lock(c++) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1728323370a1154117.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论