C++线程-条件变量和生产者消费者模型

这个内容比较重要,并且面试很容易被问道。所以把他单独拿出来了。

条件变量

条件变量是一种线程同步机制

  • 当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。
  • 为了保护共享资源,条件变量需要和互斥锁结合一起使用。
  • 比较常见的是生产/消费者模型(高速缓存队列)。

生产者消费者的示意图:
在这里插入图片描述
左边是生产这,右边是消费者,生产者先把需要处理的数据放在缓冲队列中,然后向消费者发出通知。消费者接到通知后,从缓存队列中把数据拿出来,然后处理他们。

一个生活中的例子:

某企业的售后服务系统,客服小姐姐负责收集用户需求。他们生成工单,然后派发给维修工人。在这个例子中,客服小姐姐是生产者,工单是数据的任务队列,维修工人是数据的消费者。

再来看一个实际开发中的例子:
在这里插入图片描述
假设这是一个网站的后台服务程序,大型网站的后台服务程序有明确的分工。网络通信的线程负责接收客户端的业务请求。然后把业务请求放入队列。后台工作线程负责处理业务请求。在实际开发中,生产者可以是一个线程。也可以是多个线程。而消费者一般是多个线程。这多个线程有一个通俗的名称叫线程池。
大家想想看。如果只用互斥锁。能实现生产者-消费者模型吗?
多个线程共享缓存队列。读写数据的时候可以用互斥锁来保护,但是当生产者把数据放入缓存队列后,如何通知消费者线程呢?互斥锁好像做不到。

条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些
线程才会被唤醒。
C++11的条件变量提供了两个类:

  • condition_variable:只支持与普通mutex搭配,效率更高。
  • condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)。

对于代码生产者要做的事情就是:

  1. 生产数据
  2. 把数据放入缓冲队列
  3. 向消费者线程发出通知

消费者是一个死循环,就像工人一样,等待派单,处理工单,不断循环

#include<iostream>
#include<algorithm>
#include<string>
#include<mutex>
#include<deque>
#include<queue>
#include<condition_variable>
using namespace std;
class AA {
	mutex m_mutex;//互斥锁
	condition_variable m_cond;//条件变量
	queue<string, deque<string> > m_q;//缓冲队列,底层容器用deque
public:
	void incache(int num)//生产数据,num指定数据的个数
	{
		lock_guard<mutex> lock(m_mutex);//申请加锁
		for (int i = 0;i < num;i++) {
			static int bh = 1;//超女编号
			string message = to_string(bh++) + "号超女";//拼接出一个数据
			m_q.push(message);//把生产出来的数据入队 
		}
		m_cond.notify_one();//唤醒一个当前条件变量堵塞的线程 
	}
	void outcache() {//消费者线程任务函数
		
		while (true) {
			string message;//存放出队的数据
			{//这个作用域的作用是让他立刻释放锁,数据处理完出队之后立刻释放锁
				//把互斥锁转化成unique_lock<mutex>,并申请加锁
				unique_lock<mutex> lock(m_mutex);
				while (m_q.empty())//如果队列非空,不进入循环,之间处理数据,必须用循环,不能用if 
					m_cond.wait(lock);//等待生产者的幻想信号

				//数据出队
				message = m_q.front();
				m_q.pop();
			}

			//处理出队的数据(把数据消费掉)
			this_thread::sleep_for(chrono::milliseconds(1));//假设处理数据需要1毫秒
			cout << "线程:" << this_thread::get_id() << "," << message << endl;
		}

	}

};
int main() {
	AA aa;
	thread t1(&AA::outcache,&aa);//创建消费者线程t1
	thread t2(&AA::outcache, &aa);//创建消费者线程t1
	thread t3(&AA::outcache, &aa);//创建消费者线程t1
	this_thread::sleep_for(chrono::seconds(2));//休眠2秒
	aa.incache(3);//生产三个数据

	this_thread::sleep_for(chrono::seconds(2));//休眠3秒
	aa.incache(5);//生产三个数据

	t1.join();
	t2.join();
	t3.join();
}

代码分析

条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些
线程才会被唤醒。
C++11的条件变量提供了两个类:

condition_variable:只支持与普通mutex搭配,效率更高。
condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)。

包含头文件:<condation_variable>

condition_varibale类

  1. condition_variable()默认构造函数。

  2. condition_variable(const condition_variable &)=delete 禁止拷贝。

  3. condition_variable& condition_variable:operator=(const condition_variable &)=delete禁止赋值

  4. notify_one()通知一个等待的线程

  5. notify_all()通知全部等待的线程
    当我们使用notify_one()的时候是这样的:一个线程占用生产者生产的全部资源。
    在这里插入图片描述
    当使用notify_all()的时候是这样的:全部线程都占用:当时多个资源的时候用这个all。
    在这里插入图片描述
    所以当生产的数据有一个的时候用notify_one合适,当生产的数据有多个的时候用notify_all合适。

  6. wait(unique_lock<mutex> lock)阻塞当前线程,直到通知到达。

  7. wait(unique_lock<mutex> lock,Pred pred)循环的阻塞当前线程,直到通知到达且谓词满足。

  8. wait_for(unique_lock<mutex> lock,时间长度)

  9. wait_for(unique_lock<mutex> lock,时间长度,Pred pred)

  10. wait_until(unique_lock<mutex> lock,时间点)

  11. wait_until(unique_lock<mutex> lock,时间点,Pred pred)

条件变量的wait(mutex)函数

  • 把互斥锁解锁
  • 阻塞,等待被唤醒
  • 给互斥锁加锁
#include<iostream>
#include<algorithm>
#include<string>
#include<mutex>
#include<deque>
#include<queue>
#include<condition_variable>
using namespace std;
class AA {
	mutex m_mutex;//互斥锁
	condition_variable m_cond;//条件变量
	queue<string, deque<string> > m_q;//缓冲队列,底层容器用deque
public:
	void incache(int num)//生产数据,num指定数据的个数
	{
		lock_guard<mutex> lock(m_mutex);//申请加锁
		for (int i = 0;i < num;i++) {
			static int bh = 1;//超女编号
			string message = to_string(bh++) + "号超女";//拼接出一个数据
			m_q.push(message);//把生产出来的数据入队 
		}
		m_cond.notify_one();//唤醒一个当前条件变量堵塞的线程 
		//m_cond.notify_all();//唤醒全部当前条件变量堵塞的线程
	}
	void outcache() {//消费者线程任务函数
		
		while (true) {
			string message;//存放出队的数据
			//这个作用域的作用是让他立刻释放锁,数据处理完出队之后立刻释放锁
			//把互斥锁转化成unique_lock<mutex>,并申请加锁
			unique_lock<mutex> lock(m_mutex);
			//条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据
			while (m_q.empty())//如果队列空,进入循环,负责直接处理数据,必须用循环,不能用if 
				m_cond.wait(lock);//等待生产者的唤醒信号

			//m_cond.wait(lock, [this] {return !m_q.empty();});
			//上面和while函数一样,也有一个while
			
			//数据出队
			message = m_q.front();
			m_q.pop();cout << "线程:" << this_thread::get_id() << "," << message << endl;
			lock.unlock();//手工解锁,这样就不用作用域了
			
			//处理出队的数据(把数据消费掉)
			this_thread::sleep_for(chrono::milliseconds(1));//假设处理数据需要1毫秒
			
		}

	}

};
int main() {
	AA aa;
	thread t1(&AA::outcache,&aa);//创建消费者线程t1
	thread t2(&AA::outcache, &aa);//创建消费者线程t2
	thread t3(&AA::outcache, &aa);//创建消费者线程t3
	this_thread::sleep_for(chrono::seconds(2));//休眠2秒
	aa.incache(3);//生产三个数据

	this_thread::sleep_for(chrono::seconds(2));//休眠3秒
	aa.incache(5);//生产三个数据

	t1.join();
	t2.join();
	t3.join();
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值