C++多线程编程(3) 异步操作类 std::future std::promise std::async

C++中提供了异步操作相关的类:
1. std::future: 异步结果的传输通道,可以很方便的获取线程函数的返回值。

    在C++中,如果希望获取线程函数的返回值,就不能直接通过thread.join()得到结果,这时就必须定义一个变量,在线程函数中去给这个变量赋值,然后执行join,最后得到结果,这是一个非常繁琐的过程。C++11 的 thread 库提供了future,用来访问异步操作的结果。为什么会被命名为future呢:这是因为一个异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取,这个异步操作的结果是一个未来的期待值,所以被称为future,future相当于提供了一个获取异步操作结果的通道。可以通过同步等待的方式获取结果,也可以通过查询future的状态来获取异步操作的结果:

   future的状态为 future_status , 共有三种状态:

1.Deferred: 异步操作还没有开始

2. Ready: 异步操作已经完成

3. Timeout: 异步操作超时

2.std::promise:

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

具体的关系我个人理解为如下的图:

在线程函数的外部创建std::promise,然后将它作为线程函数的参数传入,在线程函数中为其赋值。然后在线程函数外部通过promise 的 get_future()方法创建 std::future,再通过future的get方法获取变量的值即可。所以从图中可以看出,future相当于异步结果的输出通道。而这个通道是位于std::promise内部的。

使用方法如下:

使用future的get方法,获得任务执行的返回值, 但是如果当前任务尚未执行, 任务会触发立即执行, 并且堵塞当前线程,直到任务完成

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>

void task(std::promise<int> &prom , int para)   // promise作为函数的参数
{
	int res = para * 10;
	prom.set_value_at_thread_exit(res);     // 将线程中需要输出的值存放到promise中
}


int main()
{
	std::promise<int> promise_;    // 创建promise
	std::thread t1(task, std::ref(promise_), 12);    // 将promise作为参数传入到线程函数中
	t1.join();

	std::future<int> f = promise_.get_future();    // 创建通道      通道输出数据的类型
	std::cout << "The task output " << f.get() << std::endl;
    return 0;
}

当然,也可以在一个线程中执行线程函数,在拎一个线程中获取线程函数中需要输出的值

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>

void task(std::promise<int> &prom , int para)   // promise作为函数的参数
{
	int res = para * 10;
	prom.set_value_at_thread_exit(res);     // 将线程中需要输出的值存放到promise中
}

void get_task_value(std::future<int> &future)   // future作为函数的参数
{
	std::cout << "The task output " << future.get() << std::endl;
}

int main()
{
	std::promise<int> promise_;    // 创建promise
	std::future<int> future = promise_.get_future();   // 创建通道
	
	std::thread t1(task, std::ref(promise_), 12);    // 将promise作为参数传入到线程函数中   线程函数
	std::thread t2(get_task_value, std::ref(future));  // 获取线程函数值的线程
	
	t1.join();
	t2.join();

    return 0;
}

可以通过查询future的状态来获取异步任务的执行情况,例如,可以在上面的代码中添加future的状态查询,直到任务完成为止。

 future的状态为 future_status , 共有三种状态:

1.Deferred: 异步操作还没有开始

2. Ready: 异步操作已经完成

3. Timeout: 异步操作超时

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

void task(std::promise<int> &prom , int para)   // promise作为函数的参数
{
	std::this_thread::sleep_for(std::chrono::seconds(2));   // 线程延时2s
	int res = para * 10;
	prom.set_value_at_thread_exit(res);     // 将线程中需要输出的值存放到promise中
}

void get_task_value(std::future<int> &future)   // future作为函数的参数
{
	std::future_status status;
	do
	{
		status = future.wait_for(std::chrono::milliseconds(200));    // 等待时间
		if (status == std::future_status::deferred)
		{
			// 异步操作还没有开始
			std::cout << "异步操作还没有开始" << std::endl;
		}
		else if (status == std::future_status::timeout)
		{
			// 异步操作超时, 就是还没有完成的意思
			std::cout << "异步操作超时" << std::endl;
		}
		else
		{
			// 异步操作已经完成
			std::cout << "The task output " << future.get() << std::endl;
		}
	} while (status != std::future_status::ready);

	//std::cout << "The task output " << future.get() << std::endl;
}

int main()
{
	std::promise<int> promise_;    // 创建promise
	std::future<int> future = promise_.get_future();   // 创建通道
	
	std::thread t1(task, std::ref(promise_), 12);    // 将promise作为参数传入到线程函数中   线程函数
	std::thread t2(get_task_value, std::ref(future));  // 获取线程函数值的线程
	
	t1.join();
	t2.join();

    return 0;
}

运行结果:

关于shared_future:

使用与futrue相似,shared_futrue类型允许使用第二次, 并且使用获得结果与第一次一样,如果有一场,抛出的异常也是一样的futrue共享状态在get调用后就解除,下次调用会发生报错。 但是使用shared_future的时候,get方法可调用多次,但是结果是一样的,例如:

3.std::package_task

std::package_task包装了一个可调用对象的包装类,它将函数和future绑定起来,(std::promise是将数据和future绑定起来),以便异步调用。package_task和promise有点类似,promise保存的是一个共享的状态值,package_task保存的是一个函数。(其实感觉书上的这句话并没有表述清楚)

实际上,std::future提供了一个访问异步操作结果的机制,它和线程是一个级别的,属于低层次对象。在std::future上的高一层是std::package_task和std::promise. 它们内部都有future以便访问异步操作结果。

std::promise包装的是一个异步操作,如果需要获取异步操作的返回值,就用std::package_task

std::promise包装的是一个值,如果需要获取异步操作中的某个值,就可以使用std::promise

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

double sum(double x, double y)
{
	return x + y;
}


int main()
{
	// 使用package_task获取返回值
	std::packaged_task<double(double, double)> task(sum);   // <double(double, double)>函数参数
	// 获取future
	std::future<double> future = task.get_future();

	// 需要将任务移动到线程中进行异步操作
	std::thread t1(std::move(task), 2.4, 5.1);
	t1.join();

	std::cout << "The sum is " << future.get() << std::endl;
    return 0;
}

future的wait_for方法是超时等待返回结果,而wait方法知识等待异步操作完成,没有返回值,将上面的方法改写为wait_for方法:
例如:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int factorial(int n)   // 计算阶乘
{
	std::this_thread::sleep_for(std::chrono::milliseconds(100));
	if (n == 1)
		return n;
	return n * factorial(n - 1);
}

void get_result(std::future<int> &future)   // 获取结果
{
	while (future.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout)   /// 等待线程结束
	{
		std::cout << "..";
	}
	std::cout << std::endl;
	std::cout << "The result is " << future.get() << std::endl;
}

int main()
{
	// 使用package_task获取返回值
	std::packaged_task<int(int)> task(factorial);   // <double(double, double)>函数参数
	// 获取future
	std::future<int> future = task.get_future();

	// 需要将任务移动到线程中进行异步操作
	std::thread t1(std::move(task), 7);      // 任务处于调用线程中,移动到线程中进行处理
	std::thread t2(get_result, std::ref(future));   // get_result不用move

	t1.join();
	t2.join();

	//std::cout << "The sum is " << future.get() << std::endl;
	system("pause");
    return 0;
}

线程异步操作函数async:

std::async可以用来直接创建异步的task,异步任务返回的结果保存在future中,只需要调用future.get()方法就可以获取到返回值。如果不关注异步任务的结果,则可以调用future.wait()方法,等待任务完成。

async的原型是:

std::async(std::launch::async | std::launch::deferred, f, args);

其中:

第一个参数是创建线程的方式:

std::launch::async在调用async时就创建线程。

std::launch::deferred延迟加载方式创建线程,直到调用了future的get或者wait方法时才会创建线程

第二个参数是线程函数

第三个参数是线程函数的参数

基本用法:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int factorial(int n)   // 计算阶乘
{
	std::this_thread::sleep_for(std::chrono::milliseconds(100));
	if (n == 1)
		return n;
	return n * factorial(n - 1);
}


void func()
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::cout << "This function has no operation" << std::endl;
}


int main()
{
	std::future<int> future1 = std::async(std::launch::deferred, factorial, 5);   // 线程函数有返回值
	std::future<void> future2 = std::async(std::launch::async, func);             // 线程函数没有参数

	future2.wait();     // 调用wait()
	std::cout << "Factorial is " << future1.get() << std::endl;   //调用get()
	system("pause");
    return 0;
}

也可以使用async来创建线程,一般也推荐这样做:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

void func1()
{
	for (int i = 0; i < 1000; i++)
	{
		std::cout << "Thread Id is " << std::this_thread::get_id() << std::endl;
	}
}

int main()
{
	std::future<void> future1 = std::async(std::launch::async, func1);             // 线程函数没有参数
	std::future<void> future2 = std::async(std::launch::async, func1);             // 线程函数没有参数

	future1.wait();
	future2.wait();     // 调用wait()

	system("pause");
    return 0;
}

最后介绍一下std::shared_future

shared_future的使用与futrue相似,因为futrue共享状态在get调用后就解除,下次调用会发生报错,而shared_futrue类型允许使用第二次, 并且使用获得结果与第一次一样,如果有一场,抛出的异常也是一样的。创建方式如下:
std::shared_futrue f = std::async(task).share();

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int func1(int n)
{
	return n * 10;
}

int main()
{
	std::shared_future<int> future = std::async(std::launch::async, func1, 2).share();             // 线程函数没有参数

	std::cout << future.get() << std::endl;
	std::cout << future.get() << std::endl;   // 第二次调用

	system("pause");
    return 0;
}

应该优先使用async取代线程的创建,它更加方便的实现了异步调用。

 

-------------------------------------------------------分割线------------------------------------------------------------

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值