C++11异步编程

news2025/3/1 16:26:53

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1、std::future和std::shared_future
    • 1.1 std:future
    • 1.2 std::shared_future
  • 2、std::async
  • 3、std::promise
  • 4、std::packaged_task

前言

C++11提供了异步操作相关的类,主要有std:future、std:promise 和std:package task。std:future作为异步结果的传输通道,可以很方便地获取线程函数的返回值;。std:promise 用来包装一个值、将数据和future绑定起来,方便线程赋值; std:package_task 用来包装一个可调用对象,将函数和future绑定起来,以便异步调用。

1、std::future和std::shared_future

1.1 std:future

C++11中增加的线程,使得我们可以非常方便地创建和使用线程,但有时会有些不便,比如希望获取线程函数的返回结果,就不能直接通过thread.join()得到结果,这时就必须定义一个变量,在线程函数中去给这个变量赋值,然后执行join(),最后得到结果,这个过程是比较烦琐的。thread 库提供了future用来访问异步操作的结果,因为一个异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取,这个异步操作的结果是一个未来的期待值,所以被称为future,future 提供了获取异步操作结果的通道。我们可以以同步等待的方式来获取结果,可以通过查询future 的状态( future_status) 来获取异步操作的结果。futrue_status有3种状态:

1、Deferred:异步操作还没开始
2、Ready:异步操作已经完成
3、Timeout:异步操作超时

future原型

template <class T>  future;
template <class R&> future<R&>;     
template <>         future<void>

std::future是一个类模板

成员方法
在这里插入图片描述
通过future构造函数构造的future对象是无效的,此时调用future.get()可能会抛异常,需要我们捕获

int main()
{
	future<int> fu;
	try
	{
		cout << fu.get() << endl;
	}
	catch (const std::exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

在这里插入图片描述

如果没有初始化future,则表明它是无效的,此时我们也可以使用valid判断future对象是否有效

int main()
{
	future<int> fu;
	if (fu.valid())
	{
		fu.get();
	}
	else
	{
		cout << "no state" << endl;
	}
	return 0;
}

std::asyanc是std::future的高级封装, 一般我们不会直接使用std::futrue,而是使用对std::future的高级封装std::async

mutex mtx;
string GetCurrentDateTime()
{
	SYSTEMTIME stime;
	GetLocalTime(&stime);
	stringstream ss;
	unique_lock<mutex> lock(mtx);
	ss << "[" << this_thread::get_id() << "]" << "current time: " << stime.wYear << "/" << stime.wMonth \
		<< "/" << stime.wDay << "/" << stime.wHour << "/" << stime.wMinute << "/" << stime.wSecond << endl;
	this_thread::sleep_for(chrono::seconds(2));
	return ss.str();
}

int main()
{
	future<string> fu = async(GetCurrentDateTime);//执行一个异步任务来获取当前时间
	cout << this_thread::get_id() << " : main do something..." << endl;
	cout << fu.get() << endl;
	return 0;
}

在这里插入图片描述

上述代码中,main线程通过async创建一个新线程去执行异步任务(获取当前系统时间),在新线程执行异步任务的过程中,main线程可以不必阻塞,但在调用get方法时,如果新线程没有返回,就会被阻塞

我们可以通过wait_for进行超时等待返回结果,如果新线程还没返回,就做其他事情,再重复wait_for

mutex mtx;
string GetCurrentDateTime()
{
	SYSTEMTIME stime;
	GetLocalTime(&stime);
	stringstream ss;
	unique_lock<mutex> lock(mtx);
	ss << "[" << this_thread::get_id() << "]" << "current time: " << stime.wYear << "/" << stime.wMonth \
		<< "/" << stime.wDay << "/" << stime.wHour << "/" << stime.wMinute << "/" << stime.wSecond << endl;
	this_thread::sleep_for(chrono::seconds(3));//休眠3秒
	return ss.str();
}

int main()
{
	std::future_status status;
	future<string> fu = async(GetCurrentDateTime);
	cout << this_thread::get_id() << " : main do something..." << endl;
	do{
		status = fu.wait_for(chrono::milliseconds(500));
		if (status == std::future_status::deferred)
		{
			cout << "deferred:异步操作还没开始," << "main do something" << endl;
		}
		else if (status == std::future_status::timeout)
		{
			cout << "timeout:在规定时间内异步操作还未完成," << "main do something" << endl;
		}
		else if (status == std::future_status::ready)
		{
			cout << "ready:异步操作已经完成,当前时间为:" << fu.get();
		}
	} while (status != std::future_status::ready);
	return 0;
}

在这里插入图片描述

上述代码中,main线程调用了wait_for(chrono::milliseconds(500)),表示mian线程等待500ms,如果在500ms内,子线程完成异步任务,则立刻返回,如果超过500ms还没有完成异步任务,则不会继续等待,立刻返回

future不允许直接赋值或者拷贝,但允许移动赋值

void func(){}

int main()
{
	future<void> fu1;
	future<void> fu2 = async(std::launch::async, func);
	if (fu2.valid())
	{
		cout << "fu2.valid()=true" << endl;
		cout << "move..." << endl;
		fu1 = std::move(fu2);
		cout << "fu2.valid()=" <<fu2.valid() << endl;
		cout << "fu1.valid()=" << fu1.valid() << endl;
	}
	else
	{
		cout << "fu2.valid()=false" << endl;
	}
	return 0;
}

在这里插入图片描述

get成功之后,future对象将不再有效

string func()
{
	return "fl";
}

int main()
{
	future<string> fu = async(std::launch::async, func);
	if (fu.valid())
	{
		cout << "fu.get() begin fu.valid()=" << fu.valid() << endl;
		cout << "fu.get()=" << fu.get() << endl;
		cout << "fu.get() after fu.valid()=" << fu.valid() << endl;
	}
	return 0;
}

在这里插入图片描述

1.2 std::shared_future

shared_futrue原型

template <class T>  shared_future;
template <class R&> shared_future<R&>; 
template <>         shared_future<void>; 

C++11中的std: :shared_future是个模板类。与std::future类似,std::shared_future提供了一种访问异步操作结果的机制。不同于std::future,std::shared_futrue允许多个线程等待同一个共享状态。不同于std::future仅支持移动操作,std::shared_future既支持移动操作也支持拷贝操作,而多个std::shared_future对象可以引用相同的共享状态

前面谈到,对于std::future对象,如果就绪,使用get后,future对象就无效了,如果再使用get,则会抛异常。使用std::shared_future就很好的解决了这个问题

string func()
{
	return "fl";
}

int main()
{
	shared_future<string> fu = async(std::launch::async, func);
	if (fu.valid())
	{
		cout << "fu.get()=" << fu.get() << endl;
		cout << "fu.get()=" << fu.get() << endl;
		cout << "fu.get()=" << fu.get() << endl;
	}
	else
	{
		cout << "fu.valid()=false" << endl;
	}
	return 0;
}

在这里插入图片描述

支持拷贝和赋值

string func()
{
	return "fl";
}

int main()
{
	shared_future<string> fu = async(std::launch::async, func);
	shared_future<string> fu1 = fu;
	shared_future<string> fu2(fu1);
	if (fu.valid())
	{
		cout << "fu.get()=" << fu.get() << endl;
		cout << "fu1.get()=" << fu.get() << endl;
		cout << "fu2.get()=" << fu.get() << endl;
	}
	else
	{
		cout << "fu.valid()=false" << endl;
	}
	return 0;
}

在这里插入图片描述

2、std::async

std::async比std::future和std::promise、std::packaged和std::thread更高一层,它可以用来直接创建异步的task,异步任务返回的结果也保存在std::future中,需要获取异步任务的结果时,只需要调用future.get()方法即可,如果不关注异步任务的结果,只是简单地等待任务完成时的话,则调用future.wait()方法

async原型

template <class Fn, class... Args>
  future<typename result_of<Fn(Args...)>::type>
    async (Fn&& fn, Args&&... args);
    
template <class Fn, class... Args>
  future<typename result_of<Fn(Args...)>::type>
    async (launch policy, Fn&& fn, Args&&... args);

后者比前者多了一个参数:launch policy,这个表示线程的创建策略,有两种策略,默认的策略是立即创建线程

  1. std::launch::async:在调用async时就开始创建线程
  2. std::launch::deferred:延迟加载方式创建线程。在调用async时不创建线程,知道调用了future的get或者wait时才创建线程

第二个参数:表示线程的入口函数,必须是一个可调用对象
第三个参数:表示线程函数的参数

std::async的操作,其实相当于封装了std::promise、std::packaged_task加上std::thread

使用std::launch::deferred作为参数

mutex mtx;
string GetCurrentDateTime()
{
	SYSTEMTIME stime;
	GetLocalTime(&stime);
	stringstream ss;
	unique_lock<mutex> lock(mtx);
	ss << "[" << this_thread::get_id() << "]" << "current time: " << stime.wYear << "/" << stime.wMonth \
		<< "/" << stime.wDay << "/" << stime.wHour << "/" << stime.wMinute << "/" << stime.wSecond << endl;
	cout << "in GetCurrentDateTime()" << endl;
	return ss.str();
}

int main()
{
	future<string> fu = async(std::launch::deferred, GetCurrentDateTime);
	this_thread::sleep_for(chrono::milliseconds(500));
	cout << "main id is " << this_thread::get_id() << endl;
	cout << fu.get() << endl;
	return 0;
}

在这里插入图片描述
可以看到GetCurrentDateTime()也是有main线程执行的。在上述的执行顺序中,main线程先打印了自己的线程id,然后调用fu.get()时进入GetCurrentDateTime()内部,完成任务后才打印的时间。因此就能很好的证明deferred参数确实是在创建async时,不创建线程执行任务,而是在调用get方法时,由main线程去执行异步任务。

将参数改为async,执行结果为:

在这里插入图片描述

可以看到,在调用async时就开始创建线程去执行异步任务。

如果我们没有指定第一个参数,默认是async | deferred,具体采用哪个参数,取决于操作系统
在这里插入图片描述

3、std::promise

std::promise将数据和future绑定起来,为获取线程函数中的某个值提供便利,在线程函数中为外面传进来的promise赋值,在线程函数执行完成之后就可以通过promise的future获取该值了。值得注意的是:取值是间接地通过promise内部提供的future来获取的。

std::promise原型

template <class T>  promise;		 //空模板
template <class R&> promise<R&>;     //用于线程间交流对象
template <>         promise<void>;   //用于交流无状态事件

成员方法
在这里插入图片描述

举个例子,简单了解一下promise和future是如何关联的

void func1(std::promise<int>& promObj)
{
	cout << this_thread::get_id() << "设置promise的值" << endl;
	promObj.set_value(5);
}

void func2(std::future<int>& fut)
{
	cout << this_thread::get_id() << "访问promise的值" << endl;
	cout << this_thread::get_id() << ":" << fut.get() << endl;
}

int main()
{
	std::promise<int> prom;
	std::future<int> fut = prom.get_future();
	std::thread t1(func1, std::ref(prom));
	std::thread t2(func2, std::ref(fut));
	t1.join();
	t2.join();
	return 0;
}

在这里插入图片描述

在上述代码中,我们定义一个std::promise<int> prom,并让其与std::future<int> fut进行关联。让t1线程去调用fun11,t2线程去调用func2。如果t2线程在func2中调用fut.get()时,而fut并没有被设置,此时f2线程会被阻塞,直到t1线程在func1中调用promObj.set_value(5),例如:

在这里插入图片描述
std::promise的operator=没有拷贝语义,即std::promise的普通赋值函数是被delete掉了,只有move语义,因此std::promise对象是禁止拷贝的

int main()
{
	std::promise<int> prom;
	std::future<int> fut = prom.get_future();
	std::promise<int> prom1 = prom;
	return 0;
}

在这里插入图片描述

采用set_value_at_thread_exit演示一下死锁

mutex mtx;
void func1(std::promise<int>& promObj)
{
	this_thread::sleep_for(chrono::microseconds(500));
	promObj.set_value_at_thread_exit(1);
	unique_lock<mutex> lock(mtx);
	cout << this_thread::get_id() << "设置promise的值" << endl;
}

void func2(std::future<int>& fut)
{
	unique_lock<mutex> lock(mtx);
	cout << this_thread::get_id() << "访问promise的值" << endl;
	cout << this_thread::get_id() << ":" << fut.get() << endl;
}

int main()
{
	std::promise<int> prom;
	std::future<int> fut = prom.get_future();
	thread t1(func1, std::ref(prom));
	thread t2(func2, std::ref(fut));
	t1.join();
	t2.join();
	return 0;
}

在这里插入图片描述
为了让func2中的代码先执行,在func1的开始调用了sleep_for(chrono::microseconds(500)),休眠500微秒。首先f2线程拿到锁,执行到fut.get()会被阻塞,因为此时fut还未就绪。线程t1使用set_value_at_thread_exit(1),表明设置指定值为1,但是只有在t1线程结束时,才提醒。因此t1线程将被阻塞在unique_lock lock(mtx)上,而t2线程被阻塞在fut.get()上,所以产生了死锁。

使用set_value_at_thread_exit好处就是确保某个线程的退出,另一个线程才能被获取到某个值,例如:

void func1(std::promise<int>& promObj)
{
	promObj.set_value(1);
	cout << this_thread::get_id() << "设置promise的值" << endl;
	cout << "do something..." << endl;
	cout << "do something..." << endl;
	cout << "do something..." << endl;
	cout << "do something..." << endl;
}

void func2(std::future<int>& fut)
{
	cout << this_thread::get_id() << "访问promise的值" << endl;
	cout << this_thread::get_id() << ":" << fut.get() << endl;
}

int main()
{
	std::promise<int> prom;
	std::future<int> fut = prom.get_future();
	thread t1(func1, std::ref(prom));
	thread t2(func2, std::ref(fut));
	t1.detach();
	t2.join();
	return 0;
}

在这里插入图片描述
t1线程进行了分离,t2线程先结束,也就导致了main线程结束了,t1线程在后台执行,并没有将"do something…"打印到终端上。因此可以使用set_value_at_thread_exit使t2线程等待t1线程结束

在这里插入图片描述

一个线程将异常传递给另一个线程

void func1(std::promise<void>& p)
{
	try
	{
		cout << this_thread::get_id() << " throw error" << endl;
		throw exception("this is func1 throw error");
	}
	catch (const std::exception& e)
	{
		p.set_exception_at_thread_exit(std::current_exception());
	}
}

void func2(std::future<void>& fu)
{
	try
	{
		if (fu.valid())
			fu.get();
	}
	catch (const std::exception e)
	{
		cout << this_thread::get_id() << " error is " << e.what() << endl;
	}
}

int main()
{
	std::promise<void> p;
	std::future<void> fu = p.get_future();
	thread t1(func1, std::ref(p));
	thread t2(func2, std::ref(fu));
	t1.join();
	t2.join();
	return 0;
}

在这里插入图片描述

4、std::packaged_task

std:packaged _task 包装了一个可调用对象的包装类(如function、lambda expression、bind expression和another function object),将函数和future绑定起来,以便异步调用,它和std:promise在某种程度上有点像,promise保存了一个共享状态的值,而packaged task 保存的是一个函数。

std::packaged_task原型

template <class T> packaged_task;     // undefined
template <class Ret, class... Args> class packaged_task<Ret(Args...)>;

成员函数

在这里插入图片描述

简单案例

int f(int x, int y) { return std::pow(x, y); }

void task_lambda()
{
	std::packaged_task<int(int, int)> task([](int a, int b) {
		return std::pow(a, b);
	});
	std::future<int> result = task.get_future();
	//auto result = task.get_future();
	task(2, 2);

	std::cout << "task_lambda:\t" << result.get() << '\n';
}

void task_bind()
{
	std::packaged_task<int()> task(std::bind(f, 2, 3));
	std::future<int> result = task.get_future();
	//auto result = task.get_future();
	task();

	std::cout << "task_bind:\t" << result.get() << '\n';
}

void task_thread()
{
	std::packaged_task<int(int, int)> task(f);
	std::future<int> result = task.get_future();
	std::thread task_td(std::move(task), 2, 4);
	task_td.join();

	std::cout << "task_thread:\t" << result.get() << '\n';
}

int main()
{
	task_lambda();
	task_bind();
	task_thread();
	return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/404789.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3电商项目实战-结算支付 2【03-结算-对话框组件封装、04-结算-收货地址-切换】

文章目录03-结算-对话框组件封装04-结算-收货地址-切换03-结算-对话框组件封装 目的&#xff1a;实现一个对话框组件可设置标题&#xff0c;动态插入内容&#xff0c;动态插入底部操作按钮&#xff0c;打开关闭功能。 大致步骤&#xff1a; 参照xtx-confirm定义一个基础布局实…

MFC常用控件使用(文本框、编辑框、下拉框、列表控件、树控件)

简介 本文章主要介绍下MFC常用控件的使用&#xff0c;包括静态文本框(Static Text)、编辑框(Edit Control)、下拉框(Combo Box)、列表控件(List Control)、树控件(Tree Control)的使用。 创建项目 我们选择 文件->新建->新建项目&#xff0c;选择MFC程序 选择基于对话…

二叉树的三种遍历

二叉树的遍历可以有&#xff1a;先序遍历、中序遍历、后序遍历先序遍历&#xff1a;根、左子树&#xff0c;右子树中序遍历&#xff1a;左子树、根、右子树后序遍历&#xff1a;左子树、右子树、根下面是我画图理解三种遍历&#xff1a;二叉树里都是分为左子树和右子树。分治思…

Linux文件基础I/O

文件IO文件的常识基础IO为什么要学习操作系统的文件操作C语言对于函数接口的使用接口函数介绍如何理解文件文件描述符重定向更新给模拟实现的shell增加重定向功能为什么linux下一切皆文件&#xff1f;缓冲区为什么要有缓冲区缓冲区对应的刷新策略缓冲区的位置在哪里文件的常识 …

VSCode:添加SSH远程连接

有的时候我们的代码保存于远程服务器&#xff0c;通过VSCode可以通过SSH进行连接&#xff0c;完成远程的编辑。在VSCode的扩展中安装Remote - SSH点击左侧工具栏的远程资源管理器&#xff0c;然后点加号输入ssh的机器及用户名选择一个用于保存ssh配置文件的路径&#xff0c;默认…

Tabs Studio 5.3.0 多功能标签 Crack

在 Visual Studio 2022 和 SQL Server Management Studio 中轻松处理任意数量和类型的文档 你爱写代码&#xff0c;不会好好扫描文档找到你需要切换到的文件名&#xff0c;然后扫描文件菜单下拉列表&#xff0c;然后求助于解决方案资源管理器或搜索。只有在您需要切换到另一个…

javascript入门基础

目录 前言 引入&#xff1a;html中嵌入javascript有三种方式 0. 变量&#xff08;var、let&#xff09; 1. 函数 1.1 普通函数 和 箭头函数 1.1.2 普通函数中的this 1.1.3 箭头函数没有自己的this 1.1.4 普通函数有arguments方法&#xff0c;箭头函数没有 1.1.5 箭头函…

MS python学习(9)

开始学习第二辑 more python for beginners talking about formating https://learn.microsoft.com/en-us/shows/more-python-for-beginners/formatting-and-linting–more-python-for-beginners-2-of-20 Formating 代码格式化&#xff1a;使用pylint工具来帮助遵循PEP8(pyt…

conda创建一个地理开发环境

conda创建一个地理开发环境1. 环境内包说明2. 创建yml文件3. 创建地理开发环境使用conda安装包的时候&#xff0c;经常遇到包之间相互冲突。为了方便配置环境&#xff0c;测试了常用的地理开发所需要的各种包&#xff0c;生成了yml文件方便一键安装。 Linux下pip基本可以成功安…

手敲Mybatis(七)-细化xml语句解析和构建

前言为什么这一章节要细分之前的解析xml处理逻辑&#xff0c;原因是违反了单一原则设计&#xff0c;职责并不明确&#xff0c;将Sql语句、参数、返回值等等一切都进行解析&#xff0c;那么这种的需要拆开&#xff0c;为了后面可维护可扩展&#xff0c;例如Mapper级别的有mapper…

k8s client-go源码解析之informer三

Informer&#xff08;三&#xff09; 注意&#xff1a;本文内容为学习笔记&#xff0c;内容为个人见解&#xff0c;不保证准确性&#xff0c;但欢迎大家讨论何指教。 觉得文章不错请关注跟博客及github 本篇介绍DeltaFIFO及indexer。 informer大致工作流程如下&#xff1a; …

顺序表来喏!!!

前言&#xff1a;还记得前面的文章&#xff1a;《通讯录的实现》吗&#xff1f;通讯录的完成就借助了顺序表这种数据结构&#xff01;&#xff01;&#xff01;那么今天我们就来介绍我们的顺序表介绍顺序表前&#xff0c;我们来了解一下线性表的概念线性表&#xff1a;线性表&a…

mysql笔试题18道

部门表、员工表、薪水等级表 1.取得每个部门最高薪水人员名称 第一步&#xff1a;取得每个部门最高薪水作为临时表t select deptno,max(sal) as maxSal from emp group by deptno 第二步&#xff1a;临时表t与emp表连接条件 e.deptnot.deptno and e.salt.maxSal select …

Spring - Spring IoC 容器相关面试题总结

文章目录01. Spring IoC 和依赖注入是什么&#xff1f;02. Spring IoC 的优点和缺点分别是什么&#xff1f;03. Spring IoC 有什么作用和功能&#xff1f;04. Spring 依赖注入的方式&#xff1f;05. Spring 构造器注入和 setter 方法注入的区别&#xff1f;06. Spring 依赖注入…

嵌入式系统实验——【玄武F103开发板】按key1熄灭两个LED灯、松开恢复点亮

这里写目录标题一、任务目标&#xff08;一&#xff09;分析二、设计思路&#xff08;一&#xff09;开启KEY1对应的GPIOx时钟1.找到KEY1&#xff08;PE3&#xff09;所在的GPIOx端口2.开启GPIOE端口时钟3.清空PE3的端口位4.设置PE3的端口位为输出模式的上拉模式5.一个易错点&a…

二分——力扣篇

二分——力扣篇搜索旋转排序数组搜索旋转排序数组II寻找旋转排序数组中的最小值寻找旋转排序数组中的最小值II搜索旋转排序数组 定理一&#xff1a;只有在顺序区间内才可以通过区间两端的数值判断target是否在其中。 定理二&#xff1a;判断顺序区间还是乱序区间&#xff0c;只…

案例学习20之内存长期占用导致系统缓慢

前言&#xff1a; 发现问题&#xff0c;解决问题&#xff0c;是贯穿整个项目开发过程的事情&#xff0c;能够处理更多的问题&#xff0c;随着经验的丰富&#xff0c;提前预知更多的问题&#xff0c;让问题不出现是最好的解决问题方式。 问题背景&#xff1a; 项目运行过程中出现…

基于redis实现点赞数,点击数,排行榜

使用场景 对于某些视频或者文章有点赞数和点击数, 通过这些数据就可以进行排行榜的功能了 使用异步队列 redis的集合 A.php //点击数 $redis->zIncrBy(click.:.date(Ymd),1,$videoId); //点赞数 $redis->zIncrBy(love.:.$videoId,1,$$user); //获取当前video的播放数…

PMP项目管理项目范围管理

目录1 项目范围管理概述2 规划范围管理3 收集需求4 定义范围5 创建 WBS6 确认范围7 控制范围1 项目范围管理概述 项目范围管理包括确保项目做且只做所需的全部工作&#xff0c;以成功完成项目的各 个过程。管理项目范围主要在于定义和控制哪些工作应在项目内&#xff0c;哪些工…

界面原型设计

引用锤子科技视觉设计总监——罗子雄在重庆TEDx活动上说的一小段话: 每当我们看到一些美妙的设计的时候,很多人心里面会有一种冲动,这种冲动会让你们想去创造一些新的东西,创造一些美妙的事物。 我们常说用户体验用户体验,用户使用你的软件,第一个会接触的是什么?没错,…