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取代线程的创建,它更加方便的实现了异步调用。

 

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

 

posted @   Alpha205  阅读(365)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示