这个内容比较重要,并且面试很容易被问道。所以把他单独拿出来了。
条件变量
条件变量是一种线程同步机制
- 当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。
- 为了保护共享资源,条件变量需要和互斥锁结合一起使用。
- 比较常见的是生产/消费者模型(高速缓存队列)。
生产者消费者的示意图:
左边是生产这,右边是消费者,生产者先把需要处理的数据放在缓冲队列中,然后向消费者发出通知。消费者接到通知后,从缓存队列中把数据拿出来,然后处理他们。
一个生活中的例子:
某企业的售后服务系统,客服小姐姐负责收集用户需求。他们生成工单,然后派发给维修工人。在这个例子中,客服小姐姐是生产者,工单是数据的任务队列,维修工人是数据的消费者。
再来看一个实际开发中的例子:
假设这是一个网站的后台服务程序,大型网站的后台服务程序有明确的分工。网络通信的线程负责接收客户端的业务请求。然后把业务请求放入队列。后台工作线程负责处理业务请求。在实际开发中,生产者可以是一个线程。也可以是多个线程。而消费者一般是多个线程。这多个线程有一个通俗的名称叫线程池。
大家想想看。如果只用互斥锁。能实现生产者-消费者模型吗?
多个线程共享缓存队列。读写数据的时候可以用互斥锁来保护,但是当生产者把数据放入缓存队列后,如何通知消费者线程呢?互斥锁好像做不到。
条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些
线程才会被唤醒。
C++11的条件变量提供了两个类:
condition_variable
:只支持与普通mutex
搭配,效率更高。condition_variable_any
:是一种通用的条件变量,可以与任意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();//唤醒一个当前条件变量堵塞的线程
}
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();
}