【C++11 多线程】async执行异步任务(九)
一、std::async介绍
std::async
用于创建异步任务,实际上就是创建一个线程异步执行相应任务,它接受回调(即函数或函数对象)作为参数。
std::async
就是异步编程的高级封装,相当于封装了std::promise
、std::packaged_task
加上std::thread
,基本上可以代替std::thread
的所有事情。
template <class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async (launch policy, Fn&& fn, Args&&... args);
std :: async
返回一个std :: future
,该值存储由std :: async
执行的函数对象返回的值。
std :: async
中的第一个参数是启动策略,它控制std :: async
的异步行为。我们可以使用 3 种不同的启动策略来创建,即:
std::launch::async
:它保证了异步行为,即传递的函数将在单独的线程中执行。std::launch::deferred
:非异步行为,即当其他线程将来调用 get() 以访问共享状态时,将调用 Function。std::launch::async | std::launch::deferred
:它是默认行为。使用此启动策略,它可以异步运行或不异步运行,具体取决于系统上的负载。但是我们无法控制它。
二、案例分析
假设我们必须从数据库中获取一些数据(字符串),并从文件系统中的文件中获取一些数据。然后,我需要合并两个字符串并进行打印。
在单线程中,我们这样做:
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
std::string fetchDataFromDB(std::string recvData) {
// 确保函数要5秒才能执行完成
std::this_thread::sleep_for(std::chrono::seconds(5));
// 处理创建数据库连接、获取数据等事情
return "DB_" + recvData;
}
std::string fetchDataFromFile(std::string recvData) {
// 确保函数要5秒才能执行完成
std::this_thread::sleep_for(std::chrono::seconds(5));
// 处理获取文件数据
return "File_" + recvData;
}
int main() {
// 获取开始时间
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
// 从数据库获取数据
std::string dbData = fetchDataFromDB("Data");
// 从文件获取数据
std::string fileData = fetchDataFromFile("Data");
// 获取结束时间
auto end = std::chrono::system_clock::now();
// 计算消耗时间
auto diff = std::chrono::duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
// 组装数据
std::string data = dbData + " :: " + fileData;
// 输出组装的数据
std::cout << "Data = " << data << std::endl;
return 0;
}
/*
输出:
Total Time Taken = 10 Seconds
Data = DB_Data :: File_Data
*/
由于两个函数fetchDataFromDB()
和fetchDataFromFile()
均需要 5 秒钟,并且都在单个线程中运行,因此,消耗的总时间将为 10 秒钟。
现在,从数据库和文件中获取数据是彼此独立的,而且非常耗时。因此,我们可以并行运行它们。有两种方法:
- 一种方式是创建一个新的线程传递一个
promise
作为线程函数的参数,并在调用线程中从关联的std::future
对象获取数据。 - 另一种简单的方法是使用
std::async
。
三、使用函数指针调用std::async作为回调
现在让我们修改上面的代码,并使用std::async
异步调用fetchDataFromDB()
,即:
std::future<std::string>resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
std::string dbData = resultDromDB.get()
std::async()
做以下事情:
- 它会自动为我们创建一个线程(或从内部线程池中选择)和一个 promise 对象。
- 然后将 std :: promise 对象传递给线程函数,并返回关联的 std::future 对象。
- 当我们传递的参数函数退出时,其值将在此 promise 对象中设置,因此最终返回值将在 std::future 对象中可用。
现在更改上面的示例,并使用std :: async
从数据库异步读取数据,即:
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
std::string fetchDataFromDB(std::string recvData) {
// 确保函数要5秒才能执行完成
std::this_thread::sleep_for(std::chrono::seconds(5));
// 处理创建数据库连接、获取数据等事情
return "DB_" + recvData;
}
std::string fetchDataFromFile(std::string recvData) {
// 确保函数要5秒才能执行完成
std::this_thread::sleep_for(std::chrono::seconds(5));
// 处理获取文件数据
return "File_" + recvData;
}
int main() {
//获取开始时间
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
// 创建异步线程,从数据库获取数据
std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
//从文件获取数据
std::string fileData = fetchDataFromFile("Data");
//从DB获取数据
//数据在future<std::string>对象中可获取之前,将一直阻塞
std::string dbData = resultFromDB.get();
//获取结束时间
auto end = std::chrono::system_clock::now();
// 计算消耗时间
auto diff = std::chrono::duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
// 组装数据
std::string data = dbData + " :: " + fileData;
// 输出组装的数据
std::cout << "Data = " << data << std::endl;
return 0;
}
/*
输出:
Total Time Taken = 5 Seconds
Data = DB_Data :: File_Data
*/
因为有一个任务是异步的,在 5 秒内可以同时执行完两个任务。
四、更多示例
std::async
使用一个 callable 作为一个工作包。在本例中,它可以是个函数、函数对象或者匿名函数。
#include <future>
#include <iostream>
std::string helloFunction(const std::string& s) {
return "Hello C++11 from " + s + ".";
}
class HelloFunctionObject {
public:
std::string operator()(const std::string& s) const {
return "Hello C++11 from " + s + ".";
}
};
int main() {
// 带函数的future
auto futureFunction = std::async(helloFunction, "function");
// 带函数对象的future
HelloFunctionObject helloFunctionObject;
auto futureFunctionObject = std::async(helloFunctionObject, "function object");
// 带匿名函数的future
auto futureLambda = std::async([](const std::string& s) {return "Hello C++11 from " + s + "."; }, "lambda function");
std::cout << futureFunction.get() << "\n"
<< futureFunctionObject.get() << "\n"
<< futureLambda.get() << std::endl;
std::cout << std::endl;
}
/*
输出:
Hello C++11 from function.
Hello C++11 from function object.
Hello C++11 from lambda function.
*/
std::async
调用在两端的 future 和 promise 创建了一个数据通道。通过 future 的 get() 调用,future 收到了它的工作包的返回值。
参考:
C++11 Multithreading – Part 9: std::async Tutorial & Example
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!