接上一篇文章:《探索C++多线程》:future源码(一),在本文中将对std::promise、std::packaged_task进行分析。
std::promise
promise对象可以保存某一T类型的值,该值可以被future对象(可能在另一个线程中)获取,因此promise也提供了一种同步手段。
在构造时,promise对象与一个新的共享状态(通常是std::future)相关联,他们可以存储T类型的值或从std::exception派生的异常。共享状态可以通过调用成员get_future来与future相关联,调用后,两个对象共享同一个状态:
1、promise对象是异步的提供者,并在某一时刻为共享状态设置值;
2、future对象是异步返回共享状态的值,它可以获取异步状态的值(必要时阻塞等待状态就绪);
我们来看一个例子:
#include <iostream> // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
using namespace std;
void print_int(future<int>& fut) {
int x = fut.get(); // 阻塞获取,当在另一个线程中调用了promise::set_value()后,不再阻塞,并立即返回共享状态的值
cout << "value: " << x << '\n';
}
int main() {
promise<int> prom; // 创建promise对象
future<int> fut = prom.get_future(); // 与future关联
thread th1(print_int, ref(fut)); // 将future对象传到一个线程中
prom.set_value(10); // 设置值
// 与future同步
th1.join();
getchar();
return 0;
}
在上述代码中,pormise对象调用get_future()方法,返回与之关联的future。
std::promise::get_future()
该方法返回一个与promise对象共享状态相关联的future对象,返回的future对象,可以通过promise对象来访问共享状态的值或异常,每个promise对象共享状态返回一个future对象。
调用了此方法后,promise对象将在某一时刻(通过设置值或异常)使共享状态准备就绪,否则,它将在自动析构包含future_error类型异常的情况下准备就绪。
std::promise::set_value()
设置共享状态的值,若一个future对象关联了同一共享状态,并且调用了future::get()来阻塞的获取值时,经调用promise::set_value()后将不再阻塞并返回一个值。
std::promise::set_value_at_thread_exit()
设置共享状态的值,但并不立即将共享状态的标志设置为ready;相反地,是在线程退出时设置为就绪状态。如果某个future对象与promise对象的共享状态相关联,并且该future对象正在调用get(),则会被阻塞,当线程退出时,不再阻塞并且返回共享状态的值。
std::packaged_task()
包装了一个可调用的目标,并且允许异步的获取其结果。与std::function类似,但其结果将自动的传递给future对象(可以在另一个线程中调用future::get()获取该结果)。
我们来看一个例子:
#include <iostream> // std::cout
#include <future> // std::packaged_task, std::future
#include <chrono> // std::chrono::seconds
#include <thread> // std::thread, std::this_thread::sleep_for
using namespace std;
// 被包装的函数:秒倒数计数
int countdown(int from, int to) {
for (int i = from; i != to; --i) {
cout << i << '\n';
this_thread::sleep_for(chrono::seconds(1));
}
cout << "Lift off!\n";
return from - to;
}
int main() {
packaged_task<int(int, int)> tsk(countdown); // 建立 packaged_task
future<int> ret = tsk.get_future(); // 获取 future 对象
thread th(move(tsk), 10, 0); // 启动从10到0向下计数的线程
int value = ret.get(); // 阻塞的等待任务结束,并获取返回值
cout << "The countdown lasted for " << value << " seconds.\n";
th.join();
return 0;
}
packaged_task对象内部包含两个元素:
1、被包装的任务:如函数指针,指向成员或函数对象的指针;
2、存储共享状态:用于存储任务的返回值,可以通过future::get()来异步访问共享状态的值。
std::packaged_task::get_future()
我们来看一段代码:
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread
using namespace std;
int triple (int x) {
return x * 3;
}
int main () {
packaged_task<int(int)> tsk (triple); // 包装任务
future<int> fut = tsk.get_future(); // 获取 future 对象
thread(move(tsk), 33).detach(); // 建立线程,并调用任务
int value = fut.get(); // 阻塞等待任务完成,并获取结果
cout << "The triple of 33 is " << value << ".\n";
return 0;
}
在上述代码中,主线程与任务线程剥离,任务线程交由系统管理,主线程调用fut.get()将阻塞地等待任务线程计算完毕,才能获取到共享状态的值。
std::packaged_task::reset()
同样的,我们来看一段代码:
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread
using namespace std;
int triple(int x) {
return x * 3;
}
int main() {
packaged_task<int(int)> tsk(triple);
future<int> fut = tsk.get_future();
tsk(33);
cout << "The triple of 33 is " << fut.get() << ".\n";
// 再次使用同一个 packaged_task 对象
tsk.reset();
fut = tsk.get_future();
thread(move(tsk), 99).detach();
cout << "Thre triple of 99 is " << fut.get() << ".\n";
getchar();
return 0;
}
重置任务:在保持同一包装任务的同时,使用新的共享状态来重置任务对象,这允许再次调用存储任务。