C++11多线程(四) 条件变量

本文详细介绍了C++11中的条件变量condition_variable及其使用方法,包括wait、notify_one、notify_all等函数的用途及代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++11多线程(四) 条件变量

condition_variable

condition_variable是一个类,由字面意思可以很清楚地看出它与条件相关。也就是条件变量

condition_variable没有特别的构造方法,所以我们直接声明就好了。

它的拷贝构造函数和等号运算符重载函数也被禁止了。

condition_variable s;

条件变量也就是我们所说的通过判断(bool)来对锁进行控制,不太理解没有关系,我们接着往下看。

condition_variable::wait()

首先来看一下condition_variable::wait()的函数源码

void wait(unique_lock<mutex>& _Lck) //第一种
		{	// wait for signal
		_Cnd_waitX(_Mycnd(), _Lck.mutex()->_Mymtx());
		}

template<class _Predicate> 
		void wait(unique_lock<mutex>& _Lck, _Predicate _Pred)//第二种
		{	// wait for signal and test predicate
		while (!_Pred())
			wait(_Lck);
		}

从里面我们可以清楚的看到,condition_variable是和unique_lock配合使用的。

原因如下

  • unique_lock具有lock(),unlock()函数。

  • unique_lock重载了带右值引用参数的拷贝构造函数和赋值运算符重载函数,这样它就可以应用在函数传参或者参数返回中使用。

    记不得的小伙伴回顾上一节

那wait()的作用是什么呢?

  • 将当前线程的状态设置成睡眠状态,直至被唤醒。
  • 调用传入的mutex的unlock() , 释放出锁。

好了,源码看不懂的话我们就用更简单的语言讲解。

第一个构造函数要求我们传入一个unique_lock

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

class Test
{
public:
	Test() = default;
	~Test() = default;
	void f1();
	void f2();
private:
	mutex mtx;
	condition_variable con_var;		//定义条件变量
	};
void Test::f1() {
	unique_lock<mutex> uni_lock(mtx); //创建unique_lock
	con_var.wait(uni_lock);			  //调用condition_variable::lock()
	cout << "f1()" << endl;
}
void Test::f2() {
	lock_guard<mutex> _guard_lock(mtx);
	con_var.notify_one();         //在f2中唤醒f1中的线程,并让它获得锁继续执行先前的代码。稍后讲解
	cout << "f2()" << endl;
}
int main() {
	Test T;
	while (1) {
		thread t1(&Test::f1,&T);
		thread t2(&Test::f2,&T);
		thread t3(&Test::f2, &T);
		thread t4(&Test::f2, &T);

		t1.join();
		t2.join();
		t3.join();
		t4.join();
	}
	return 0;
}

上面的代码用的是第一种wait方式,下面我们来解释一下第二种。

第二种其实也很简单,第一个参数照旧传入unique_lock,第二个参数我们传入一个bool值,来判断是否需要转换到睡眠状态。

con_var.wait(uni_lock,true/false);//这里的/表示任选其一
//但实际情况下我们一般使用匿名函数
con_var.wait(uni_lock,[]()->bool{
	return _vec.empty();//_vec是一个vector<int>,这里只是展示方法。
    					//类似的可以回顾之前封装queue的代码,当m_queue为空时线程变为睡眠状态
    					//也可以自己思考用在什么地方,有助于加深记忆
});

在实际的代码过程中,还是建议我们多多实用第二种方法,由于匿名函数的加入,使得调整线程状态变得更加灵活。

自己可以尝试打一下代码试试,这样记忆也会更深刻。

wait_until,wait_for

还记得我们之前提到的try_lock_until和try_lock_for吗?//第二节里

等待到一个时间点和等待多久。

不过try_lock_until,try_lock_for是尝试获得锁,wait_until,wait_for则是进入休眠。

condition_variable也是有两个类似方法的,

但由于其重载形式较多,这里给出最常用的两个形式。

//wait_for 阻塞线程直到被唤醒或者阻塞时长超过指定时长
if(con_var.wait_for(uni_lock,chrono::milliseconds(1000))==cv_status::timeout) {//chrono之前有提到过,C++11下的时间库
                        cout<<"Helloworld"<<endl;
                    }
//wait_until 阻塞线程直到被唤醒或者阻塞时间超过指定时间点
 if(cv.wait_until(con_var, chrono::steady_clock::now() + std::chrono::milliseconds timeout(100)){
 						cout<<"Helloworld"
 					}

notify_one,notify_all

还记得上面提到的那段代码吗?

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

class Test
{
public:
	Test() = default;
	~Test() = default;
	void f1();
	void f2();
private:
	mutex mtx;
	condition_variable con_var;		//定义条件变量
};
void Test::f1() {
	unique_lock<mutex> uni_lock(mtx,); //创建unique_lock
	con_var.wait(uni_lock);//调用condition_variable::lock()
	cout << "f1()" << endl;
}
void Test::f2() {
	lock_guard<mutex> _guard_lock(mtx);
	con_var.notify_one();    
	cout << "f2()" << endl;
}
int main() {
	Test T;
		thread t1(&Test::f1, &T);
		thread t2(&Test::f2, &T);
		thread t3(&Test::f2, &T);
		thread t4(&Test::f2, &T);

		t1.join();
		t2.join();
	return 0;
}

有休眠就有唤醒。当线程条件就绪时,我们就可以将其唤醒,这里使用的是notify_one.

notify_one,notify_all两个都可以用作唤醒,他们也都是condition_variable的一个成员函数。

从名称也可以看出他们的作用

  • notify_one是用来唤醒一个线程的。

  • notify_all是用来唤醒所有休眠线程的。

    值得注意的是notify_one是唤醒睡眠状态里的随机一个线程,并不能指定唤醒某个睡眠线程。

    想想刚刚的模拟读取m_que代码

    con_var.wait(uni_lock,true/false);//这里的/表示任选其一
    //但实际情况下我们一般使用匿名函数
    con_var.wait(uni_lock,[]()->bool{//[]()这里是匿名函数的使用,不懂的查阅一下相关资料,很实用的特性
    	return m_que.empty();//m_que是一个queue<int>,这里只是展示方法。
        					//可以回顾之前封装queue的代码,去自己实现一下当m_queue为空时,线程变为睡眠状态,防止Pop()抛出异常
        					//也可以自己思考用在什么地方,有助于加深记忆
    });
    m_que.Pop();
    
    //另一个函数中我们就可以去唤醒
    m_que.push(1);   //当我们传入数字后,m_que就不再为空了。
    con_var.notify_one();//我们就可以唤醒随机一个线程去读取
    					//当然我们也可以使用notify_all()去唤醒所有读取线程,但要注意异常
    

condition_variable_any

既然提到了condition_variable,就顺带一提condition_variable_any。

condition_variable只能接受unique_lock锁,但condition_variable_any却能接受任意形式的锁。

其余基本是一样的,因为本人用的不是很多就只能引个头,感兴趣的同学自行查询相关资料。

condition_variable的用法很简单多练习很容易上手,但C++所定义的condition远不如此,我也只能介绍一些我常用的。

好了,condition_variable的介绍就到这里,谢谢您的观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值