C11线程管理:条件变量
1、简介
C11提供另外一种用于等待的同步机制,它可以阻塞一个或者多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程。条件变量要和互斥量配合起来使用。
condition_variable,配合std::unique_lock<std::mutex>进行wait操作。
condition_variable_any,和任意带有lock、unlock语意 的mutex搭配使用,比较灵活,但是效率比condition_variable低。
条件变量的使用过程如下:
a.拥有条件变量的线程获取互斥量。
b.循环检查某个条件,如果条件不满足,则阻塞线程直到满足;如果条件满足,则向下执行。
c.某个线程满足条件并执行完成之后,调用notify_one或者notify_all来唤醒一个或者多个线程。
2、实践
可以用条件变量来实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间的读取,比如半同步半异步线程池的同步队列。
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include<condition_variable>
#include <list> template<typename T>
class SyncQueue
{
public:
SyncQueue(int maxSize) :m_maxSize(maxSize){} void Put(const T & t)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsFull())
{
std::cout << "缓冲区满了,需要等待..." << std::endl;
m_notFull.wait(m_mutex);
} m_queue.push_back(t);
m_notEmpty.notify_one();
} void Take(const T & t)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsEmpty())
{
std::cout << "缓冲区空了,需要等待..." << std::endl;
m_notEmpty.wait(m_mutex);
} t = m_queue.front();
m_queue.pop_front(t);
m_notFull.notify_one();
} bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
} bool Full()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
} size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
} private:
bool IsFull() const
{
return m_queue.size() == m_maxSize;
} bool IsEmpty() const
{
return m_queue.empty();
} private:
std::list<T> m_queue; //缓冲区
std::mutex m_mutex; //互斥量
std::condition_variable_any m_notEmpty; //不为空的条件变量
std::condition_variable_any m_notFull; //没有满的条件变量
int m_maxSize; //同步队列最大容量
};
这个队列中,没有满的情况下可以插入数据,如果满了,则会调用m_notFull阻塞线程等待,等待消费线程取出数据之后发出一个未满的通知,然后前面阻塞的线程会被唤醒继续往下执行;如果队列为空,不能取出数据,调用m_notEmpty来阻塞当前线程,等待插入数据的线程插入数据发出不为空的通知,唤醒被阻塞的线程,往下执行读出数据。
条件变量的wait方法还有个重载方法,可以接受一个条件。
std::lock_guard<std::mutex> locker(m_mutex);
while (IsFull())
{
std::cout << "缓冲区满了,需要等待..." << std::endl;
m_notFull.wait(m_mutex);
}
可以写为这样:
std::lock_guard<std::mutex> locker(m_mutex);
m_notFull.wait(locker, [this]{ return !IsFull();});
两种写法都一样,后者代码更加简洁,条件变量先检查判断式是否满足条件,如果满足,重新获取mutex,结束wait,继续往下执行;如果不满足条件,则释放mutex,将线程置为waiting状态,继续等待。
需要注意的是,wait函数会释放掉mutex,而lock_guard还拥有mutex,他只在出了作用域之后才会释放掉mutex,所以这时并不会释放,但是执行wait会提前释放,而在wait提前释放掉锁之后,会处于等待状态,在notify_one/all唤醒之后,会先获取mutex,相当于之前的mutex又获取到了,所以在出作用域的时候,lock_guard释放锁不会产生问题。
在这种情况下,如果用unique_lock语意更准确,因为unique_lock不像lock_guard一样只能在析构的时候才能释放锁,它可以随时释放锁,在wait的时候让uniq_lock释放锁,语意更加准确。
上述例子中,可以用unique_lock来替换掉lock_guard,condition_variable来替换掉condition_variable_any,会使代码更加清晰,效率也更高。
3、超时等待
除了wait还可以使用超时等待函数std::condition_variable::wait_for和std::condition_variable::wait_until。
与 std::condition_variable::wait() 类似,不过 wait_for 可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for 返回,剩下的处理步骤和 wait() 类似。
与 std::condition_variable::wait_for 类似,但是 wait_until 可以指定一个时间点,在当前线程收到通知或者指定的时间点 abs_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_until 返回,剩下的处理步骤和 wait_for() 类似。
// condition_variable::wait_for example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <chrono> // std::chrono::seconds
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status std::condition_variable cv; int value; void read_value() {
std::cin >> value;
cv.notify_one();
} int main ()
{
std::cout << "Please, enter an integer (I'll be printing dots): \n";
std::thread th(read_value); std::mutex mtx;
std::unique_lock<std::mutex> lck(mtx);
while (cv.wait_for(lck,std::chrono::seconds())==std::cv_status::timeout) {
std::cout << '.' << std::endl;
}
std::cout << "You entered: " << value << '\n'; //等待th线程执行完
th.join(); return ;
}
如果用wait_until,只需要将条件改为时间点即可:
while (cv.wait_for(lck,std::chrono::seconds())==std::cv_status::timeout)
while (cv.wait_until(lck, std::chrono::system_clock::now() +std::chrono::seconds()) == std::cv_status::timeout)
C11线程管理:条件变量的更多相关文章
- Java线程:条件变量、原子量、线程池等
一.条件变量 条件变量实现了java.util.concurrent.locks.Condition接口,条件变量的实例化就是通过一个Lock对象上调用newCondition()方法获得的,这样条件 ...
- python线程的条件变量Condition的用法实例
Condition 对象就是条件变量,它总是与某种锁相关联,可以是外部传入的锁或是系统默认创建的锁.当几个条件变量共享一个锁时,你就应该自己传入一个锁.这个锁不需要你操心,Condition 类会 ...
- linux Posix线程同步(条件变量) 实例
条件变量:与互斥量一起使用,暂时申请不到某资源时进入条件阻塞等待,当资源具备时线程恢复运行 应用场合:生产线程不断的生产资源,并通知产生资源的条件,消费线程在没有资源情况下进入条件等待,一直等到条件信 ...
- pThreads线程(三) 线程同步--条件变量
条件变量(Condition Variables) 参考资料:http://game-lab.org/posts/posix-thread-cn/#5.1 条件变量是什么? 条件变量为我们提供了另一种 ...
- Linux线程同步——条件变量
互斥锁是用来给资源上锁的,而条件变量是用来等待而不是用来上锁的. 条件变量用来自动阻塞一个线程,直到某特殊情况发生为止. 通常条件变量和互斥锁同时使用. 和条件变量使用有关的几个重要函数: int p ...
- C11线程管理:原子变量&单调函数
1.原子变量 C++11提供了原子类型std::atomic<T>,可以使用任意类型作为模板参数,使用原子变量就不需要使用互斥量来保护该变量,用起来更加简洁. 举个例子,如果要做一个计数器 ...
- C11线程管理:异步操作
1.异步操作 C++11提供了异步操作相关的类,std::future.std::promise和std::package_task.std::future作为异步结果的传输通道,方便的获取线程函数的 ...
- C11线程管理:线程创建
1.线程的创建 C11创建线程非常简单,只需要提供线程函数就行,标准库提供线程库,并可以指定线程函数的参数. #include <iostream> #include <thread ...
- C11线程管理:互斥锁
1.概述 锁类型 c11提供了跨平台的线程同步手段,用来保护多线程同时访问的共享数据. std::mutex,最基本的 Mutex 类,独占的互斥量,不能递归使用. std::time_mutex,带 ...
随机推荐
- erlang驱动使用mysql-otp
Magnus Ahltorp的Mysql Driver里面介绍emysql的缺陷: 1. 隔离不够好, 2.不能伸缩 mysql-otp使用1个进程1个mysql连接,隔离得很好.推荐使用. mysq ...
- 通过js读取元素的样式
/* * 通过元素.style.样式只能获取到内联样式的值,就是style写在元素里面的值,不能获取嵌入式和外联样式的值 * 所以如果要获取除内联样式后的值,就不能通过这个获取 * alert(box ...
- SSM整合CRUD操作(一)
http://www.cnblogs.com/loger1995/p/6352179.html?utm_source=itdadao&utm_medium=referral 说明:这是我刚开始 ...
- Hadoop HDFS环境搭建
1,首先安装JDK,下面如果JDK出现安装错误,可以卸载 卸载 1.卸载用 bin文件安装的JDK方法: 删除/usr/java目录下的所有东西 2.卸载系统自带的jdk版本方法: 查看自带的jdk: ...
- Struts2 应知应会
struts.xml 文件的 action 的配置: Struts2 中结果类型的配置来自于下面: 其中: dispatcher:转发技术,转发到一个 jsp 视图 redirect:重定向到一个 j ...
- Java多线程 -join用法
阿里面试官问我这个问题,我仔细总结了一下: 参考:sleep.yield.wait.join的区别(阿里面试) 1. join()介绍 join() 定义在Thread.java中.join() 的作 ...
- 在Delphi中动态地使用SQL查询语句 Adoquery sql 参数 冒号
在Delphi中动态地使用SQL查询语句 在一般的数据库管理系统中,通常都需要应用SQL查询语句来提高程序的动态特性.下面介绍如何在Delphi中实现这种功能.在Delphi中,使用SQL查询语句的途 ...
- CodeForces 860D Wizard's Tour
题意 给出一张无向图,要求找出尽量多的长度为2的不同路径(边不可以重复使用,点可以重复使用) 分析 yzy:这是原题 http://www.lydsy.com/JudgeOnline/problem. ...
- FragmentTransaction add 和 replace 区别 转
使用 FragmentTransaction 的时候,它提供了这样两个方法,一个 add , 一个 replace . add 和 replace 影响的只是界面,而控制回退的,是事务. public ...
- TCP/IP协议详解---概述
工作之后,才发现以前在学校里学的东西忘得太快太干净了,现在需要一点点地捡起来了,要不然写几行程序会闹很多笑话会出现很多bug的.从今天开始,翻一翻<TCP/IP协议详解 卷1>这本 ...