一、问题
假设没有条件变量,对于一个生产者消费者问题,消费线程在得知队列中没有产品时,将阻塞自己。生产者线程可以给队列中放入产品,但是没有办法激活消费者线程,而消费者线程处于阻塞状态也没有办法自己激活自己。如果消费者线程使用忙等的方式,通过不断地查询来判断是否有产品,将大量的浪费CPU时间。消费者线程也可以使用睡眠+查询的方式,即发现队列中没有产品时,sleep一段时间,然后再查询。问题是睡眠多长时间?时间太长,实时性不好,时间太短,还是浪费CPU时间。
二、介绍
针对类似的场景,我们推荐您用条件变量来实现,头文件<condition_variable>。和互斥锁、信号量类似,条件变量本质也是一个全局变量,用于多线程之间关于共享数据状态变化的通信。当一个动作需要另外一个动作完成时才能进行,即:当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量。
一个条件变量可以阻塞多个线程,这些线程会组成一个等待队列。当条件成立时,条件变量可以解除线程的“被阻塞状态”。也就是说,条件变量可以完成以下两项操作:
1、阻塞线程,直至接收到“条件成立”的信号;
2、向等待队列中的一个或所有线程发送“条件成立”的信号,解除它们的“被阻塞”状态。
为了避免多线程之间发生“抢夺资源”的问题,条件变量在使用过程中必须和一个互斥锁搭配使用。
三、notify_one()
与notify_all()
notify_one()与notify_all()常用来唤醒阻塞的线程。
notify_one():因为只唤醒等待队列中的第一个线程;不存在锁争用,所以能够立即获得锁。其余的线程不会被唤醒,需要等待再次调用notify_one()或者notify_all()。
notify_all():会唤醒所有等待队列中阻塞的线程,存在锁争用,只有一个线程能够获得锁。那其余未获取锁的线程会继续尝试获得锁(类似于轮询),而不会再次阻塞。当持有锁的线程释放锁时,这些线程中的一个会获得锁。而其余的会接着尝试获得锁。
四、实例
条件变量通常有wait和notify两个动作,wait用于阻塞挂起线程A,直到另一个线程B通过notify唤醒线程A,唤醒后线程A会继续运行。这里使用消费者,生产者模型代码
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
std::mutex mut;
std::condition_variable cv;
std::vector<int> vec{};
// 消费者
void Consumer()
{
std::unique_lock<std::mutex> lock(mut);
while (vec.empty()) {
cv.wait(lock); //等待线程
}
std::cout << "consumer " << vec.size() << "\n";
}
//生产者
void Produce()
{
std::unique_lock<std::mutex> lock(mutex);
vec.push_back(1);
cv.notify_all();// 唤醒线程
std::cout << "produce \n";
}
int main()
{
std::thread thread1(Consumer);
thread1.detach(); // 主线程和子线程相互分离,互不干扰
Produce();
system("pause");
return 0;
}
参考:
条件变量的坑 - 知乎
C++11条件变量:notify_one()与notify_all()的区别_吃素的施子的博客-CSDN博客
C++11(六) 条件变量(condition_variable) - 知乎