C++11并发与多线程笔记(5)

C++11并发与多线程笔记(5),第1张

C++11并发与多线程笔记(5)
  • condition_variable、wait、notify_one、notify_all
    • 条件变量std::condition_variable、wait、notify_one
    • 上述代码深入思考
    • notify_all()
  • async、future、packaged_task、promise
    • std::async、std::future创建后台任务并返回值
    • std::packaged_task
    • std::promise
    • 小结

condition_variable、wait、notify_one、notify_all 条件变量std::condition_variable、wait、notify_one

条件变量std::condition_variable、wait()、notify_one():只能通知一个线程
线程A:等待一个条件满足
线程B:专门往消息队列中扔消息
std::condition_variable实际上是一个类,是一个和条件相关的一个类,说白了就是等待一个条件达成
这个类需要和互斥量来配合工作,用的时候我们要生成这个类的对象

class A
{
public:

	//把收到消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue() {
		for (int i = 0; i < 10000; i++) {
			cout << "inMsgRecvQueue()执行,插入一个元素!" << endl;
			my_mutex.lock();
			std::unique_lock<std::mutex> guard(my_mutex, std::adopt_lock);
			msgRecvQueue.push_back(i);
			//假如outMsgRecvQueue()正在处理一个事务,需要一段时间,而不是正卡在wait()那里等待你唤醒,那么此时这个notify_one没有效果
			//my_cond.notify_one();//我们尝试把wait()的线程唤醒,执行完这行,那么outMsgRecvQueue()里面的wait就会被唤醒
			my_cond.notify_all();

		}
	}
	void outMsgRecvQueue() {
		int command = 0;
		while (true) {
			std::unique_lock<std::mutex> guard(my_mutex);
			my_cond.wait(guard, [this] {//wait()用来等一个东西,如果第二个参数返回值为true,那么wait()直接返回,执行结束。
				//如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并堵塞到本行
				//那堵塞到什么时候为止呢?堵塞到其它线程调用notify_one()成员函数为止;
				//如果wait()没有第二个参数:my_cond.wait(guard),就跟第二个参数lambda表达式返回false效果一样
				//wait()将解锁互斥量,并堵塞到本行,堵塞到其它某个线程调用notify_one()成员函数为止;
				if (!msgRecvQueue.empty()) {
					return true;
				}
				return false;
			});
			//当其它线程用notify_one将本wait(原本是堵塞状态)唤醒后,wait就开始干活了,恢复后干什么活?
			//a)wait()不断的尝试重新获取互斥量所,如果获取不到,那么流程就卡在wait这里等着获取,如果获取到了,那么wait()就继续执行b;
			//b)
					//b.1)如果wait有第二个参数(lambda),就判断这个lambda表达式,如果lambda表达式为false,那么wait()又将解锁互斥量,并堵塞到本行
					//b.2)如果lambda表达式为true,则wait()返回,流程走下来(此时互斥锁被锁下来),
						//b.3)如果wait()没有第二个参数,则wait返回,流程走下来。
			//流程只要能走到这里来,这个互斥锁一定是锁着的,同时msgRecvQueue至少是有一条数据的。
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			cout << "outMsgRecvQueue()执行,取出一个元素" << command<<"thread_id:"<<std::this_thread::get_id() << endl;

			guard.unlock();//因为unique_lock的灵活性,所以我们可以随时unlock解锁,以免锁住太长时间
		}
	}
private:
	std::list<int> msgRecvQueue;//容器,专门用于代表玩家发过来的命令。
	std::mutex my_mutex;//创建了一个互斥量(一个互斥量就是一把锁)
	std::condition_variable my_cond;//生成一个条件变量对象
};
//main
A a;
thread outThread1(&A::outMsgRecvQueue, &a);
thread outThread2(&A::outMsgRecvQueue, &a);
thread inThread(&A::inMsgRecvQueue, &a);
outThread1.join();
outThread2.join();
inThread.join();
cout << "I love China!" << endl;
上述代码深入思考 notify_all() async、future、packaged_task、promise std::async、std::future创建后台任务并返回值

希望线程返回一个结果
std::async是个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,返回一个std::future对象,std::future对象是个模板类。
这个std::future对象里面就含有线程入口函数所返回的结果(线程返回的结果),我们可以通过调用future对象的成员函数get()来获取结果。
future:将来的意思,有人也称呼std::future提供了一种访问异步 *** 作结果的机制,就是说这个结果你可能没有办法马上拿到。
在线程执行完毕时,你就能够拿到结果了,所以大家就这么理解,这个future对象会保存一个值,在将来的某个时刻能够拿到
下面的程序通过std::future对象的get()成员函数等待线程的执行结束并返回结果;get()不拿到将来的返回值,我就卡在这里等待拿值。
我们通过额外向std::async()传递一个参数,该参数的类型是std::launch类型(枚举类型),来达到一些特殊的目的
a)std::launch::deferred:表示线程入口函数调用被延迟到std::future()的wait()或者get()函数调用的时候才执行。
那如果wait(),或者get()没有被调用,那么线程会被执行吗?没执行,实际上,根本没创建
std::launch::defered:延迟调用,并且没有创建新线程,是在主线程中调用的线程入口函数。
b)std::launch::async,在调用async函数的时候就开始创建新线程。
std::async()函数,默认就是std::launch::async标记

class B{
public:
	int mythread(int temp) {
		cout << temp << endl;
		cout << "mythread() start," << "threadid=" << std::this_thread::get_id() << endl;
		std::chrono::milliseconds dura(5000);
		std::this_thread::sleep_for(dura);
		cout << "mythread() end," << "threadid=" << std::this_thread::get_id() << endl;
		return 5;
	}
};
//int mythread(int temp) {
//	cout << temp << endl;
//	cout << "mythread() start," << "threadid=" << std::this_thread::get_id() << endl;
//	std::chrono::milliseconds dura(5000);
//	std::this_thread::sleep_for(dura);
//	cout << "mythread() end," << "threadid=" << std::this_thread::get_id() << endl;
//	return 5;
//}

//main
B b;
int temp = 12;
cout << "main," << "threadid=" << std::this_thread::get_id() << endl;
//std::future result = std::async(mythread);
std::future<int> result = std::async(&B::mythread,&b,temp);
//result.wait()//卡在这里等待mythread执行完毕,本身不返回结果
//std::future result = std::async(std::launch::deferred,&B::mythread, &b, temp);
//实际上线程入口函数std::async()函数的第一个参数为std::launch::deferred时,在主线程中std::future::get()被调用时执行
//std::future result = std::async(std::launch::async, &B::mythread, &b, temp);
cout << result.get()<<endl;//卡在这里等待mythread()执行完毕,拿到结果。
							//get只能调用一次,不能调用多次。
cout << "I love china!" << endl;
std::packaged_task

std::packaged_task:打包任务,把任务包装起来。
是个类模板,它的模板参数是各种可以调用的对象;通过std::packaged_task来把各种可调用对象包装起来,方便将来作为线程入口函数。
packaged_task包装起来的可调用对象,也是一个可调用对象。

1、mythread函数作为packaged_task的参数

int mythread(int temp) {
	cout << temp << endl;
	cout << "mythread() start," << "threadid=" << std::this_thread::get_id() << endl;
	std::chrono::milliseconds dura(5000);
	std::this_thread::sleep_for(dura);
	cout << "mythread() end," << "threadid=" << std::this_thread::get_id() << endl;
	return 5;
}
//main
cout << "main," << "threadid=" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread);//我们把函数mythread通过packaged_task包装起来
std::thread mythread(std::ref(mypt), 1);
mythread.join();
std::future<int > result = mypt.get_future();//std::future对象里面包含有线程入口函数的返回结果,这里result保存mythread的返回值
cout << result.get() << endl;

2、lambda作为packageed_list的参数

std::packaged_task<int(int)> mypt([](int mypar) {
	cout << mypar << endl;
	cout << "mythread() start," << "threadid=" << std::this_thread::get_id() << endl;
	std::chrono::milliseconds dura(5000);
	std::this_thread::sleep_for(dura);
	cout << "mythread() end," << "threadid=" << std::this_thread::get_id() << endl;
	return 5;
	});
std::thread mythread(std::ref(mypt), 1);
mythread.join();
std::future<int > result = mypt.get_future();
cout << result.get() << endl;

3、packageed_list对象放在容器中

packaged_task包装起来可调用对象后创建的对象,也是一个可调用对象
vector<std::packaged_task<int(int)>> v;
int main() {
	std::packaged_task<int(int)> mypt([](int mypar) {
	cout << mypar << endl;
	cout << "mythread() start," << "threadid=" << std::this_thread::get_id() << endl;
	std::chrono::milliseconds dura(5000);
	std::this_thread::sleep_for(dura);
	cout << "mythread() end," << "threadid=" << std::this_thread::get_id() << endl;
	return 5;
	});
	v.push_back(std::move(mypt));//入容器,这里用了移动语义,入进去之后mypt就为空
	std::packaged_task<int(int)> mypt1 =std::move(*v.begin());//移动语义
	v.erase(v.begin());
	mypt1(123);
std::promise

std::promise,类模板
我们能够在某个线程中给它赋值,然后我们可以在其他线程中,把这个值取出来用
总结:通过promise保存一个值,在将来某个时刻我们通过把一个future绑定到这个promise上来得到这个绑定的值;

void mythread(std::promise<int> &mypromise, int temp) {//大家注意第一个参数
	temp++;
	temp *= 10;
	std::chrono::milliseconds dura(5000);
	std::this_thread::sleep_for(dura);
	int result = temp;
	mypromise.set_value(result);//结果我保存到了mypromise对象里面
}
void mythread2(std::future<int>& tempfu) {//大家注意第一个参数
	int result=tempfu.get();
	cout << "mythread2 result:" << result << endl;
	return;
}
//main
	std::promise<int> mypromise;
	std::thread t1(mythread, std::ref(mypromise), 180);
	t1.join();

	//获取结果值
	std::future<int> fu1 = mypromise.get_future();

	std::thread t2(mythread2, std::ref(fu1));
	t2.join();
	int result = fu1.get();
	cout << result << endl;

小结

到底怎么用?什么时候用?
我们学习这些东西的目的,并不是要把他们都用在我们的实际开发中。
相反,如果我们能够用最少的东西能够写出一个稳定的、高效的多线程程序,更值得赞赏;
我们为了成长,必须要阅读一些高手的代码,从而快速实现自己的代码积累,我们的技术就会有一个大幅度的提升。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/874874.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存