【C++11 多线程】async执行异步任务(九)

一、std::async介绍

std::async用于创建异步任务,实际上就是创建一个线程异步执行相应任务,它接受回调(即函数或函数对象)作为参数。

std::async就是异步编程的高级封装,相当于封装了std::promisestd::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() << "
"
		<< futureFunctionObject.get() << "
"
		<< 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


原文地址:https://www.cnblogs.com/linuxAndMcu/p/14577285.html