1.什么是async
std::async:是一个函数模板,用于启动一个异步任务。它接受一个可调用的对象(如函数、Lambda表达式、函数对象)作为参数,并在一个单独的线程上异步执行对象。std::async自动管理异步任务的生命周期,并返回一个std::future对象,该对象用于获取异步操作的结果。
2.什么是future
std::future:是一个函数模板,用于表示异步操作的结果。它允许开发者在将来的某个时刻查询异步操作的状态、等待操作完成或获取操作的结果。通常,std::future对象不是直接创建的,而是与std::async、std::packaged_task或std::promise配合使用。
它的主要函数:
①.get()
作用:等待异步操作完成,并获取其结果。如果异步操作尚未完成,调用get()的线程将被阻塞,直到操作完成。一旦调用get(),它将返回异步操作的结果,并且将std::future对象将不再与任何共享状态相关联(即它将变为无效状态)
注意:get()方法只能被调用一次。如果多次尝试调用get(),将抛出std::future_error异常。
②.wait()
作用:等待异步操作完成,但不获取结果。调用wait()会阻塞当前线程,直到异步操作完成。与get()不同,调用wait()后,std::future对象仍然有效,可以继续使用get()来获取结果。
③.wait_for()
等待异步操作完成,但只等待指定的时间段。如果异步操作在该时间段内完成,wait_for()将返回std::future_status::ready。如果操作未完成但时间已过,返回std::future_status::timeout,如果异步操作尚未启动(如使用std::launched::deferred策略),则返回std::future_status::deferred。
参数:接收一个用于表示等待时间的时间段,如std::chrono::millisecond(30),std::chrono::seconds(30)。std::chrono为c++时间库。
#include<iostream>
#include<thread>
#include<future>
using namespace std;
int myfun(int a,int b) {
return a + b;
}
int main() {
future<int>myf = async(launch::deferred, myfun, 20, 30);
myf.wait();
cout << "myf==" << myf.get() << endl;
/*thread t(myfun, 10, 20);
t.join();*/
return 0;
}
3.三种启动策略
①.launch::async
行为:该策略指示std::async必须异步地执行任务,即必须在一个新的线程中启动任务,这意味着调用syd::async之后,任务将立即在新线程中开始执行,而调用线程可以继续执行其他任务。
适用场景:当你需要确保任务在单独的线程中执行,并且不希望它阻塞当前线程时,应使用此策略。
②.launch::deferred
行为:该策略指示std::deferred可以延迟任务的执行,直到调用std::future::get()或std::future::wait()方法时,任务才在当前线程中执行。如果任务从未被查询(即没有调用get()和wait()),则任务可能永远不会执行。
适用场景:当你希望仅在需要结果时才执行任务,或者当任务执行不会阻塞其他重要操作时,此策略可能会很有用。然而需要注意的是,使用此策略可能会引起潜在的并发问题,因为任务可能在查询结果时才执行。
③.launch::async|launch::deferred(默认策略)
行为:这个策略是std::launch::async和std::launch::deferred的组合,但它并不保证具体的执行方式。实际执行方式取决于实现(如编译器和库)以及系统资源。在某些情况下,任务可能会异步执行。在其他情况下,他可能会被延迟执行。
应用场景:由于此策略的行为是不确定的,因此它可能不适用于需要精确控制任务执行方式的场景。然而,作为默认策略,他提供了一种灵活的方式来启动异步任务,同时允许实现根据当前系统状况进行优化。
4.promise
#include<iostream>
#include<thread>
#include<future>
using namespace std;
//promise的用法,在一个线程中设置值,在另一个线程中获取值
void async_task(std::promise<int>prom) {
std::this_thread::sleep_for(std::chrono::seconds(1));//模拟异步操作
prom.set_value(42);
}
int main() {
std::promise<int>prom;
std::future<int>fut = prom.get_future();//获取与promise关联的future
std::thread t(async_task, std::move(prom));//启动异步任务,并将promise传给它
//prom可以移动但不可以复制,也可以写成 thread t(async_task,&prom) async_task的参数为(std::promise<int>*prom)
std::cout << "The result is " << fut.get() << std::endl;//在主线程中等待异步操作完成并获取结果
t.join();
return 0;
}
get_future():返回一个std::future对象,该对象与std::promise对象共享状态。你可以通过这个std::future对象来检索异步操作的结果。
set_value(T_value):设置异步操作的结果。调用此方法后,与std::promise关联的std::future对象将变为fulfilled状态,并且可以通过std::future::get()来检索结果。
set_exception(std::exception_ptr p):设置异步操作中抛出的异常。调用此方法后,与std::promise关联的std::future对象将变为rejected状态,并且可以通过调用std::future::get()来重新抛出异常。
注意事项:
①.std::promise的生命周期:确保std::promise对象在std:;future对象需要它之前保持有效。一旦std::promise对象被销毁,任何尝试通过std::future对象访问其结果的操作都将失败。
②.线程安全:std::promise的set_value和set_exception是线程安全的,但应该避免在多个线程同时调用它们
③.std::move的使用,在将std::promise对象传递给线程函数时,通常使用std::move来避免不必要的复制。这是因为std::promise对象通常包含非托管资源(如共享状态),复制它们可能是昂贵的或不必要的复制。
5.packaged_task
创建packaged_task对象时,需要传递一个可调用对象(如函数、lambda表达式),这个对象被封装在packaged_task中,成为一个异步任务
执行任务:任务可以通过operator()或者在新线程中调用std::thread来执行,packaged_task对象必须通过std::move传递,因为packaged_task不允许复制
调用std::future的get()方法,调用线程可以等待异步任务完成并获取其返回值,如果任务尚未完成,get()将阻塞直到结果可用