std::condition_variable可以用于异步事件的重复通知,但是有些时候可能只等待事件发生一次,比如:等待特定的航班,用条件变量大杀器有点浪费了。C++11标准库提供了几种异步任务机制。通常thread不能返回线程执行的结果(可以通过引用参数返回),而在异步处理当中很多时候都需要获得计算的结果。如果只获取结果一次那么选用future,即通过future获取了结果后,后续再通过此future获取结果将会出错。
(1) future,async,packaged_task,promise用法简介
std::future可用于异步任务中获取任务结果,但是它只是获取结果而已,真正的异步调用需要配合std::async, std::promise, std::packaged_task。这里async是个模板函数,promise和packaged_task是模板类,通常模板实例化参数是任务函数(callable object)。下面是它们的部分组合用法:假设计算任务 int task(string x);
1 async+future简单用法
future
//这里async自动创建一个后台线程(可以选取一个空闲的线程)执行任务task函数,并将计算结果保存在myFuture中,这里future的模板参数要和任务task返回类型一致为int。怎样获得任务结果呢?通常原来的线程(即创建myFuture的线程记为A,不是async执行task那个线程)可以执行其它操作,直到其想要获取task的结果时调用int x=myFuture.get()即可获得task的执行结果并保存至x中。注意若task没有执行完就调用了myFuture.get()那么线程A将会阻塞直到task完成。
2 packaged_task+future简单用法
packaged_task myPackaged(task);//首先创建packaged_task对象myPackaged其内部创建一个函数task和一个共享状态(用于返回task的结果)
future myFuture=myPackaged.get_future();//通过packaged_task::get_future()返回一个future对象myFuture用于获取task的任务结果
thread myThread(move(myPackaged),"hello world");//创建一个线程执行task任务,这里注意move语义强制将左值转为右值使用因为packaged_task禁止copy constructor,可以不创建线程,那么task任务的执行将和future结果的获取在同一个线程,这样就不叫异步了
//这里主线程可以做其它的操作
int x=myFuture.get();//线程还可以在执行一些其它操作,直到其想获取task的结果时调用此语句
3 promise+future简单用法,摘自cplusplus的范例:
#include // std::cout
#include // std::ref
#include // std::thread
#include // std::promise, std::future
void print_int (std::future& fut) {
int x = fut.get();//当promise::set_value()设置了promise的共享状态值后,fut将会通过future::get()获得该共享状态值,若promise没有设置该值那么fut.get()将会阻塞线程直到共享状态值被promise设置
std::cout << "value: " << x << '\n';//输出:value: 10
}
int main ()
{
std::promise prom; //创建一个promise对象
std::future fut = prom.get_future(); //获取promise内部的future,fut将和promise共享promise中的共享状态,该共享状态用于返回计算结果
std::thread th1 (print_int, std::ref(fut)); //创建一个线程,并通过引用方式将fut传到print_int中
prom.set_value (10); //设置共享状态值
//
th1.join();//等待子线程
return 0;
}
将主线程即需要task结果的线程称为provider,称执行任务task或上面print_int的线程为executor(这里只是为了后面表述方便,没有科学考证的)。从上面的例子可以看出,简单的同步机制都是通过设置某种共享状态然后通过future获取该共享状态达到同步。
async通过创建或者选取一个当前空闲线程执行任务,然后将计算结果保存至与此async相关的future中,期间只有存取结果值,没有其它的交互,并且是provider持有future,executor执行任务。
packaged_task是一个对象其内部持有callable object,provider创建一个下线程executor执行任务,最后provider通过相关的future获取任务计算结果。和async差不多。只有任务结果的存取,没有其它交互。
promise是provider持有,executor持有相关的future,然后provider通过promise设定共享状态的值,future获取该共享值后执行某些任务。形式上和前面两个有点相反。
(2) 细看future,async,packaged_task,promise
2.1 future可以获取计算的结果,用于不同线程间的简单同步,future的创建方式:async, packaged_task::get_future , promise::get_future这三种返回有效的future,这里有效是指future和某个共享状态关联。
future() noexcept;//创建一个空的future,其不和任何共享状态相关,注意该future是invalid的,但是其可以move
future (const future&) = delete;//禁止拷贝构造
future (future&& x) noexcept;//具有move语义
~future();//解除和某个共享状态的关联,若该future是最后一个和共享状态关联的则共享状态也被销毁,因为future是禁止拷贝的,所以这里最后一个可以理解为该future是valid的(和某个共享状态关联)
future& operator= (future&& rhs) noexcept;//移动赋值,若rhs是valid的那么赋值后rhs将不再和该共享状态关联,赋值后的future和该共享状态关联
future& operator= (const future&) = delete;//禁止拷贝赋值
shared_future share();//返回一个shared_future,shared_future允许多个shared_future和共享状态关联并且可以多次get,而future只允许一个future和共享状态关联。调用share()的future不再和共享状态关联,如future f=async(task); shared_future sh=f.share(); f.get(); f.get()*2;
bool valid() const noexcept;//若future和共享状态关联则返回true,否则返回false
T get();//若future和某个T类型的共享状态关联,那么调用future::get(),若该状态还没有准备好阻塞线程直到准备好,若状态准备好了,则返回该状态值,并将future和共享状态解绑,此future将invalid
void wait() const;//阻塞等待共享状态就绪
future_status wait_for (const chrono::duration& rel_time) const;//在rel_time时间内等待共享状态值就绪
future_status wait_until (const chrono::time_point& abs_time) const;//直到abs_time时刻等待共享状态就绪
future_status:
返回值 描述
future_status::ready 共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。
future_status::timeout 超时,即在规定的时间内共享状态的标志没有变为 ready。
future_status::deferred 共享状态包含一个 deferred 函数。
async (Fn&& fn, Args&&... args);//自动选择线程执行任务fn,args是fn的参数,若fn是某个对象的非静态成员函数那么第一个args必须是对象的名字,后面的args是fn所需的参数
async (launch policy, Fn&& fn, Args&&... args);//有三种方式policy执行任务fn
policy=launch::async表示开启一个新的线程执行fn
policy=launch::deferred 表示fn推迟到future::wait/get时才执行
policy=launch::async|launch::deferred表示由库自动选择哪种机制执行fn,和第一种构造方式async(fn,args)策略相同
2.3 packaged_task类似与std::function但是其允许异步存取结果,其内部持有一个函数调用和共享状态,该共享状态可以被packaged_task返回的future获取。
packaged_task() noexcept;//空的packaged_task对象,没有共享状态和内部函数
explicit packaged_task (Fn&& fn);//内部有共享状态和函数fn
explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);//共享状态通过alloc分配内存(该共享状态可能是个buffer)
packaged_task (const packaged_task&) = delete;//禁止拷贝构造
packaged_task (packaged_task&& x) noexcept;//具有移动语义
~packaged_task();//丢弃共享状态,若还有future和共享状态关联,那么共享状态不会被销毁直到future销毁,如果析构发生时共享状态还没有被设置那么析构将设置共享状态并在状态里加入异常
packaged_task& operator= (packaged_task&& rhs) noexcept;//rhs的共享状态将被移动,rhs将没有共享状态
ackaged_task& operator= (const packaged_task&) = delete;//禁止拷贝
bool valid() const noexcept;//若packaged_task内部有共享状态则返回true,否则返回false
future get_future();//返回一个future用以获得共享状态,该函数只能被调用一次
void operator()(Args... args);//执行fn,若成功则将结果写入共享状态,若失败则写入异常到共享状态,通过future::get()可以获取该状态
void make_ready_at_thread_exit (args... args);//结果介入共享状态,但是在该函数所在的调用线程结束后才使共享状态就绪,即该线程结束后future::get()才能获取状态值,若在写入状态值和线程没有退出期间有写入该状态的行为将抛出future_error的异常
void swap (packaged_task& x) noexcept;//交换两个packaged_task的内部share state 和 callable object
2.4 promise可以存入一个共享状态值,相关的std::future可以获取该值
promise();//空的promise对象,没有共享状态值
template promise (allocator_arg_t aa, const Alloc& alloc);//Alloc将为共享状态值开辟内存
promise (const promise&) = delete;//禁止拷贝赋值
romise (promise&& x) noexcept;//具备移动语义
~promise();//和~packaged_task()语义一样
promise& operator= (promise&& rhs) noexcept;//移动赋值,rhs不再有共享状态
promise& operator= (const promise&) = delete;
future get_future();//返回一个future和共享状态关联,可以通过此future获取共享状态的值或异常,该函数只能被调用一次
void set_value (const T& val);//设置共享状态的值
void set_value (const T& val);
void promise::set_value (R& val); // when T is a reference type (R&)
void promise::set_value (void); // when T is void
void set_exception (exception_ptr p);//设置异常指针p到共享状态中,若状态关联的future::get()会获得该异常(并解除阻塞)
void set_value_at_thread_exit (const T& val);//和packaged_task::make_ready_at_thread_exit()语义一样
void set_value_at_thread_exit (T&& val);
void promise::set_value_at_thread_exit (R& val); // when T is a reference type (R&)
void promise::set_value_at_thread_exit (void); // when T is void
void set_exception_at_thread_exit (exception_ptr p);//设置异常到共享状态中,但是在线程结束时才使共享状态就绪
void swap (promise& x) noexcept;//交换两个promise对象的共享状态
2.5 shared_future和future的区别是:一个future对象和一个共享状态相关,且转移只能通过move语义。但是多个shared_future对象可以和共享状态相关(即多对一)。std::shared_future 与 std::future 类似,但是 std::shared_future 可以拷贝、多个 std::shared_future 可以共享某个共享状态的最终结果(即共享状态的某个值或者异常)。shared_future 可以通过某个 std::future 对象隐式转换(参见 std::shared_future 的构造函数),或者通过 std::future::share() 显示转换,无论哪种转换,被转换的那个 std::future 对象都会变为 not-valid.
shared_future() noexcept;
shared_future (const shared_future& x);
shared_future (shared_future&& x) noexcept;
shared_future (future&& x) noexcept;
下面的成员函数和future差不多:
operator=
赋值操作符,与 std::future 的赋值操作不同,std::shared_future 除了支持 move 赋值操作外,还支持普通的赋值操作。
get
获取与该 std::shared_future 对象相关联的共享状态的值(或者异常)。
valid
有效性检查。
wait
等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。
wait_for
等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。(等待一段时间,超过该时间段wait_for 返回。)
wait_until
等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。(在某一时刻前等待,超过该时刻 wait_until 返回。)
3) 通常线程池采用模板实现时各线程执行的都是相同类型的任务,若采用packaged_task可以将不同类型的函数对象封转在其内部,每个线程取走一个packaged_task执行,那么线程池执行的任务可以不同。
下面是一个GUI中一个线程专门接收用户任务并压入任务队列,另一个线程专门执行用户任务
std::mutex m;
std::deque > tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread()
{
while(!gui_shutdown_message_received())//不断获取用户任务
{
get_and_process_gui_message();
std::packaged_task task;
{
std::lock_guard lk(m);
if(tasks.empty())
continue;
task=std::move(tasks.front());//
tasks.pop_front();
}
task();
}
}
std::thread gui_bg_thread(gui_thread);
template
std::future post_task_for_gui_thread(Func f)//添加任务
{
std::packaged_task task(f);
std::future res=task.get_future();
std::lock_guard lk(m);
tasks.push_back(std::move(task));//
return res;
}