条件变量是一种线程同步机制,当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒.
C++11的条件变量提供了两个类:
condition_variable:只支持与普通mutex搭配,效率更高。
condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)
包含头文件:
主要成员函数:
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() 通知全部等待的线程。
6)wait(unique_lock lock) 阻塞当前线程,直到通知到达。
7)wait(unique_lock lock,Pred pred) 循环的阻塞当前线程,直到通知到达且谓词满足。
8)wait_for(unique_lock lock,时间长度)
9)wait_for(unique_lock lock,时间长度,Pred pred)
10)wait_until(unique_lock lock,时间点)
11)wait_until(unique_lock lock,时间点,Pred pred)
template
unique_lock和lock_guard都是管理锁的辅助类,都是RAII风格( 在构造时获得锁,在析构时释放锁 )。它们的区别在于:为了配合condition_variable,unique_lock还有lock()和unlock()成员函数。
代码示例1:
#include
#include
#include // 线程类头文件。
#include // 互斥锁类的头文件。
#include // deque容器的头文件。
#include // queue容器的头文件。
#include // 条件变量的头文件。
using namespace std;
class AA
{
mutex m_mutex; // 互斥锁。
condition_variable m_cond; // 条件变量。
queue> m_q; // 缓存队列,底层容器用deque。
public:
void incache(int num) // 生产数据,num指定数据的个数。
{
lock_guard lock(m_mutex); // 申请加锁。
for (int ii=0 ; ii,并申请加锁。
unique_lock lock(m_mutex);
while (m_q.empty()) // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
m_cond.wait(lock); // 等待生产者的唤醒信号。
// 数据元素出队。
message = m_q.front(); m_q.pop();
cout << "线程:" << this_thread::get_id() << "," << message << endl;
}//添加{}自定义作用域提前解锁,数据出队后进行处理的过程无需上锁,提高效率.
// 处理出队的数据(把数据消费掉)。
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); // 生产3个数据。
this_thread::sleep_for(chrono::seconds(3)); // 休眠3秒。
aa.incache(5); // 生产5个数据。
t1.join(); // 回收子线程的资源。
t2.join();
t3.join();
}
代码示例2:
#include
#include
#include // 线程类头文件。
#include // 互斥锁类的头文件。
#include // deque容器的头文件。
#include // queue容器的头文件。
#include // 条件变量的头文件。
using namespace std;
class AA
{
mutex m_mutex; // 互斥锁。
condition_variable m_cond; // 条件变量。
queue> m_q; // 缓存队列,底层容器用deque。
public:
void incache(int num) // 生产数据,num指定数据的个数。
{
lock_guard lock(m_mutex); // 申请加锁。
for (int ii=0 ; ii,并申请加锁。
unique_lock lock(m_mutex);
// 条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据。
//while (m_q.empty()) // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
// m_cond.wait(lock); // 1)把互斥锁解开;2)阻塞,等待被唤醒;3)给互斥锁加锁。
m_cond.wait(lock, [this] { return !m_q.empty(); });
// 数据元素出队。
string 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(2); // 生产2个数据。
this_thread::sleep_for(chrono::seconds(3)); // 休眠3秒。
aa.incache(5); // 生产5个数据。
t1.join(); // 回收子线程的资源。
t2.join();
t3.join();
}
注意:
1.notify_once()只唤醒一个被当前条件变量(锁)阻塞的线程,不是随机的,是按线程阻塞队列的顺序唤醒(先进先出). notify_all()唤醒全部被当前条件变量(锁)阻塞的线程,唤醒顺序也是按线程阻塞队列的顺序唤醒. notify_all()的处理效率更高,具体使用哪个看应用环境.
2. wait(lock)函数有三个步骤:解锁,阻塞等待.重新加锁.如果不用循环而是用if,则notify_all()可能会出现虚假唤醒的情况,即队列为空但消费者线程仍运行.
3.wait(lock,谓词)重载函数第二个参数用lambda函数更便捷,该重载函数内置循环判断.
4. C++队列queue模板类的定义在
注:此课件及源代码来自B站up主:码农论坛,该文章仅作为本人学习笔记及交流学习使用,本人仅做整理并补充学习理解和知识点。