async(c++11)

一、std::async

std::async() 在某方面来说,则算是一个特化的功能,他的设计概念,就是

当要进行一个沒有马上要用其結果的复杂计算的時候,把计算丟到另一个执行序去,等到之后真的要用的時候,才去确认他跑完沒、并取得他的結果。

std::async允许您编写可能在程序主线程之外的一个或多个单独线程中运行的代码。std::async可以看作是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<T>,该值存储由std::async()执行的函数对象返回的值。函数期望的参数可以在函数指针参数之后作为参数传递给std::async()

  • std::launch::async
    • 它保证了异步行为,即传递的函数将在单独的线程中执行。
  • std::launch::deferred
    • 非异步行为,即当其他线程将来调用get()以访问共享状态时,将调用Function
  • std::launch::async | std::launch::deferred
    • 它是默认行为。使用此启动策略,它可以异步运行或不异步运行,具体取决于系统上的负载。但是我们无法控制它。

1.1 下面我们看一个示例

假设我们必须从数据库和文件系统中的文件中获取一些数据(字符串)。然后,我需要合并两个字符串并进行打印。

在一个线程中,我们将这样做:

#include <string>
#include <chrono>
#include <thread>
 
using namespace std::chrono;
 
std::string fetchDataFromDB(std::string recvdData)
{
	// Make sure that function takes 5 seconds to complete
	std::this_thread::sleep_for(seconds(4));
 
	//Do stuff like creating DB Connection and fetching Data
	return "DB_" + recvdData;
}
 
std::string fetchDataFromFile(std::string recvdData)
{
	// Make sure that function takes 5 seconds to complete
	std::this_thread::sleep_for(seconds(5));
 
	//Do stuff like fetching Data File
	return "File_" + recvdData;
}
 
int main()
{
	// Get Start Time
	system_clock::time_point start = system_clock::now();
 
	//Fetch Data from DB
	std::string dbData = fetchDataFromDB("Data");
 
	//Fetch Data from File
	std::string fileData = fetchDataFromFile("Data");
	
	std::this_thread::sleep_for(seconds(1));
 
	// Get End Time
	auto end = system_clock::now();
 
	auto diff = duration_cast < std::chrono::seconds > (end - start).count();
	std::cout << "Total Time Taken = " << diff << " Seconds" << std::endl;
 
	//Combine The Data
	std::string data = dbData + " :: " + fileData;
 
	//Printing the combined Data
	std::cout << "Data = " << data << std::endl;
 
	return 0;
}

输出结果:
在这里插入图片描述

其中fetchDataFromDB休眠5秒,fetchDataFromFile休眠4秒,然后接着休眠1秒,共10秒。

现在,从数据库和文件中获取数据是彼此独立的,而且非常耗时。因此,我们可以并行运行它们。

  1. 一种方法是创建一个新线程,将promise作为线程函数的参数传递,并在调用线程中从关联的std::Future对象获取数据。
  2. 另一种简单的方法是使用std::async

1.2 使用函数指针作为调用std::async

现在,让我们修改上面的代码,并使用std::async()初始化调用fetchDataFromDB(),即

std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");

// Do Some Stuff 
 
//Fetch Data from DB
// Will block till data is available in future<std::string> object.
std::string dbData = resultFromDB.get();

std::async() 做了以下事情:

  • 它会自动为我们创建一个线程(或从内部线程池中选择)和一个​​promise对象。
  • 然后将std::promise对象传递给线程函数,并返回关联的std::future对象。
  • 当我们传递的参数函数退出时,其值将在此promise对象中设置,因此最终返回值将在std::future对象中可用。

现在修改上面的示例中main函数,使用std::async从数据库异步读取数据,即

#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>

using namespace std::chrono;
std::string fetchDataFromDB(std::string recvdData)
{
	// Make sure that function takes 5 seconds to complete
	std::this_thread::sleep_for(seconds(5));
	//Do stuff like creating DB Connection and fetching Data
	return "DB_" + recvdData;
}
std::string fetchDataFromFile(std::string recvdData)
{
	// Make sure that function takes 5 seconds to complete
	std::this_thread::sleep_for(seconds(4));
	//Do stuff like fetching Data File
	return "File_" + recvdData;
}
int main()
{
	// Get Start Time
	system_clock::time_point start = system_clock::now();

	//use std::async to replace Fuction of fetchDataFromDB("Data");
	//注意,创建一个async对象应放在前面,产生一个thread分离出去
	std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");

	//Fetch Data from File
	std::string fileData = fetchDataFromFile("Data");

	Fetch Data from DB
	//std::string dbData = fetchDataFromDB("Data");
	std::string dbData = resultFromDB.get();

	std::this_thread::sleep_for(seconds(1));

	// Get End Time
	auto end = system_clock::now();
	auto diff = duration_cast <std::chrono::seconds> (end - start).count();
	std::cout << "Total Time Taken = " << diff << " Seconds" << std::endl;
	//Combine The Data
	std::string data = dbData + " :: " + fileData;
	//Printing the combined Data
	std::cout << "Data = " << data << std::Endl ; 返回0 ; } 

现在只需要6秒钟。其中fetchDataFromFile需要4秒,但是resultFromDB处于阻塞5秒,在加上最后1秒,共6秒。
在这里插入图片描述

1.3 使用函数对象作为回调调用std::async

/*
 * Function Object
 */
struct DataFetcher
{
	std::string operator()(std::string recvdData)
	{
		// Make sure that function takes 5 seconds to complete
		std::this_thread::sleep_for (seconds(5));
		//Do stuff like fetching Data File
		return "File_" + recvdData;
 
	}
};
 
//Calling std::async with function object
std::future<std::string> fileResult = std::async(DataFetcher(), "Data");

1.4 使用Lambda函数作为回调调用std::async

//Calling std::async with lambda function
//Calling std::async with lambda function
std::future<std::string> resultFromDB = std::async([](std::string recvdData){
		std::this_thread::sleep_for (seconds(5));
		//Do stuff like creating DB Connection and fetching Data
		return "DB_" + recvdData;
	}, "Data");



二、std::packaged_task

std::packaged_task<>是类模板,代表异步任务。它封装了

  • 可调用实体,即函数,lambda函数或函数对象。
  • 一种共享状态,用于存储由关联的回调返回或引发的异常的值。

假设我们有一个现有函数,该函数从DB中获取数据并返回

std::string getDataFromDB( std::string token)
{
	// Do some stuff to fetch the data
	std::string data = "Data fetched from DB by Filter :: " + token;
	return data;
}

现在,我们要在单独的线程中执行此功能。但是,我们如何在其他线程完成之后将结果或异常取回主线程?

一种方法是更改​​函数的声明(示例),并在函数中传递std::promise<>。在线程函数中传递std::promise<>对象之前,先从中获取关联的std::future<>并将其保留在主线程中。现在,在线程函数返回其值之前,应在传递的std::promise<>参数中设置该值,以便可以在主线程的关联std::future<>对象中使用它。

但是,如果我们使用std::packaged_task<>,则可以防止创建此std::promise<>和更改功能代码。

  • std::packaged_task<>可以包装普通函数,并使其可作为异步函数运行。

在单独的线程中调用std::packaged_task<>时,它将调用关联的回调并将返回值/异常存储在其内部共享状态中。可以通过std::future<>对象在其他线程或主函数中访问此值。

让我们从上述函数创建一个std::packaged_task<>,在单独的线程中执行,并从其future<>对象获取结果

1.创建std::packaged_task<>对象

std::package_task<>是类模板,因此我们需要将模板参数传递给packaged_task<>,即可调用函数的类型

// Create a packaged_task<> that encapsulated the callback i.e. a function
std::packaged_task<std::string (std::string)> task(getDataFromDB);

2.传递 packaged_task<> 给一个thread

std::packaged_task <>是可移动的,但不可复制,因此我们需要将其移动到线程,即

// Pass the packaged_task to thread to run asynchronously
std::thread th(std::move(task), "Arg");

由于packaged_task仅可移动且不可复制,因此我们在将其移至线程之前从其获取了std::future<>对象。

线程将执行此任务,该任务在内部调用关联的可调用实体,即我们的函数getDataFromDB()。现在,当此函数返回值时,std::packaged_task<>会将其设置为关联的共享状态,并且getDataFromDB()返回的结果或异常最终将在关联的future对象中可用。

在主函数中,从future<>对象获取结果,即

// Fetch the result of packaged_task<> i.e. value returned by getDataFromDB()
std::string data =  result.get();

get()函数将阻塞调用线程,直到可调用实体返回并且std::packaged_task <>将数据设置为可共享状态。

完整代码:

#include <iostream>
#include <thread>
#include <future>
#include <string>
 
// Fetch some data from DB
std::string getDataFromDB( std::string token)
{
	// Do some stuff to fetch the data
	std::string data = "Data fetched from DB by Filter :: " + token;
	return data;
}
 
int main()
{
 
	// Create a packaged_task<> that encapsulated the callback i.e. a function
	std::packaged_task<std::string (std::string)> task(getDataFromDB);
 
	// Fetch the associated future<> from packaged_task<>
	std::future<std::string> result = task.get_future();
 
	// Pass the packaged_task to thread to run asynchronously
	std::thread th(std::move(task), "Arg");
 
	// Join the thread. Its blocking and returns when thread is finished.
	th.join();
 
	// Fetch the result of packaged_task<> i.e. value returned by getDataFromDB()
	std::string data =  result.get();
 
	std::cout <<  data << std::endl;
 
	return 0;
}

输出:
在这里插入图片描述

2.1 使用Lambda函数创建packaged_task

#include <thread>
#include <future>
#include <string>
 
int main()
{
 
	// Create a packaged_task<> that encapsulated a lambda function
	std::packaged_task<std::string (std::string)> task([](std::string token){
		// Do some stuff to fetch the data
			std::string data = "Data From " + token;
			return data;
	});
 
	// Fetch the associated future<> from packaged_task<>
	std::future<std::string> result = task.get_future();
 
	// Pass the packaged_task to thread to run asynchronously
	std::thread th(std::move(task), "Arg");
 
	// Join the thread. Its blocking and returns when thread is finished.
	th.join();
 
	// Fetch the result of packaged_task<> i.e. value returned by getDataFromDB()
	std::string data =  result.get();
 
	std::cout <<  data << std::endl;
 
	return 0;
}

2.2 使用Function对象创建packaged_task

#include <iostream>
#include <thread>
#include <future>
#include <string>
 
/*
 * Function Object to Fetch Data from DB
 */
struct DBDataFetcher
{
	std::string operator()(std::string token)
	{
		// Do some stuff to fetch the data
					std::string data = "Data From " + token;
					return data;
	}
};
 
int main()
{
 
	// Create a packaged_task<> that encapsulated a lambda function
	std::packaged_task<std::string (std::string)> task(std::move(DBDataFetcher()));
 
	// Fetch the associated future<> from packaged_task<>
	std::future<std::string> result = task.get_future();
 
	// Pass the packaged_task to thread to run asynchronously
	std::thread th(std::move(task), "Arg");
 
	// Join the thread. Its blocking and returns when thread is finished.
	th.join();
 
	// Fetch the result of packaged_task<> i.e. value returned by getDataFromDB()
	std::string data =  result.get();
 
	std::cout <<  data << std::endl;
 
	return 0;
}



三、std::promise

类模板std::promise提供了一种存储值或异常的功能,该值或异常随后可通过该对象创建的std::future对象异步获取std::promise。一个std::promise对象与其关联的std::future对象共享数据。

  • 请注意,该std::promise对象只能使用一次。

每个promise与相关联的共享状态,它包含一些状态信息和结果可以被还未评估,评估的值(可能是无效)或评价为异常。一个promise可以在共享状态下做三件事:

  • make readypromise将结果或异常存储在共享状态中。将状态标记为就绪,并解除阻塞等待与共享状态关联的将来的任何线程。
  • release: promise放弃对共享状态的引用。如果这是最后一个这样的引用,则共享状态将被破坏。除非这是尚未准备好的std::async创建的共享状态,否则此操作不会阻塞。
  • abandon: promise存储类型为std::future_error的异常,错误代码为std::future_errc::broken_promise,使共享状态就绪,然后释放它。

让我们一步一步来看看,

在Thread1中创建一个std::promise对象。

std::promise<int> promiseObj;

到目前为止,此promise对象没有任何关联值。但是它可以保证一定有人会在其中设置值,一旦设置好值,您就可以通过关联的std::future对象获得该值。

但是现在假设线程1创建了这个Promise对象并将其传递给线程2对象。现在,线程1如何知道线程2将在此Promise对象中设置值?

答案是使用std::future对象。

每个std::promise对象都有一个关联的std::future对象,其他对象可以通过该对象获取promise设置的值

std::future<int> futureObj = promiseObj.get_future();

现在,线程1将把promiseObj传递给线程2

然后线程1将通过std :: futureget函数获取线程2std::promise中设置的值,

int val = futureObj.get();

但是如果线程2尚未设置值,则此调用将被阻塞,直到线程2promise对象中设置值,即

promiseObj.set_value(45);

示例代码及完整流程:

#include <iostream>
#include <thread>
#include <future>
 
void initiazer(std::promise<int> * promObj)
{
    std::cout<<"Inside Thread"<<std::endl;     
    promObj->set_value(35);
}
 
int main()
{
    std::promise<int> promiseObj;
    std::future<int> futureObj = promiseObj.get_future();
    std::thread th(initiazer, &promiseObj);
    std::cout<<futureObj.get()<<std::endl;
    th.join();
    return 0;
}

结果:
在这里插入图片描述

如果在设置值之前销毁了std::promise对象,则在关联的std::future对象上调用get()函数将引发异常。

另外,如果您希望线程在不同的时间点返回多个值,则只需在线程中传递多个std::promise对象,然后从关联的多个std::future对象中获取多个返回值。

在这里插入图片描述
该图显示了Promise和其他同步机制之间的区别。Promise可以在Function的中间而不是最后传递信息。


四、std::future

std::future 可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步的手段。

类模板std::future提供了一种访问异步操作结果的机制:

  • 异步操作(通过std::asyncstd::packaged_taskstd::promise创建)可以std::future为该异步操作的创建者提供一个对象。
  • 然后,异步操作的创建者可以使用多种方法来查询,等待或从中提取值std::future。如果异步操作尚未提供值,则这些方法可能会阻塞。
  • 当异步操作准备好将结果发送给创建者时,可以通过修改链接到创建者的共享状态(例如std::promise::set_value)来实现 std::future

示例:

#include <iostream>
#include <future>
#include <thread>
 
int main()
{
    // future from a packaged_task
    std::packaged_task<int()> task([]{ return 7; }); // wrap the function
    std::future<int> f1 = task.get_future();  // get a future
    std::thread t(std::move(task)); // launch on a thread
 
    // future from an async()
    std::future<int> f2 = std::async(std::launch::async, []{ return 8; });
 
    // future from a promise
    std::promise<int> p;
    std::future<int> f3 = p.get_future();
    std::thread( [&p]{ p.set_value_at_thread_exit(9); }).detach();
 
    std::cout << "Waiting..." << std::flush;
    f1.wait();
    f2.wait();
    f3.wait();
    std::cout << "Done!\nResults are: "
              << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
    t.join();
}

输出结果:

Waiting…Done!
Results are: 7 8 9

std::future 通常由某个 Provider 创建,你可以把 Provider想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值。

  • 如果共享状态的标志不为ready,则调用 std::future::get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get 返回异步任务的值或异常(如果发生了异常)。


参考文章:

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值