9 - future
当线程结束执行时,不容易取回计算的结果。可以利用std::future
获得线程运行的结果。
future简介
future
在promise
中存储结果,即promise
是结果的输入端,future
是输出端。一旦在线程中运行的函数计算出希望返回的值,就把这个值放在promise
中,最后通过future
获取这个值。
可以使用future.get()
获取结果,并将其保存在变量中。如果另一个线程尚未计算完结果,该调用将会阻塞,直到该结果值可用。只能在future
上调用一次get()
,因为第二次调用的行为是不确定的。
可先通过向future
询问结果是否可用来避免阻塞:
if (myFuture.wait_for(0))
{
int res = myFuture.get();
}
else
{
// 值还拿不到
}
promise与future
C++提供std::promise
类,可在promise
上调用set_value()
来存储结果;也能用set_exception()
来存储异常。需要注意的是,只能在特定的promise
上调用set_xxx()
一次,否则会抛出std::future_error()
异常。
以下代码展示如何使用std::promise
和std::future
:
void run(promise<int> promise)
{
// 计算中...
// 最终将结果存到promise里头
promise.set_value(42);
}
int main()
{
promise<int> myPromise;
auto theFuture(myPromise.get_future());
// 创建子线程进行计算, promise无法复制, 需要移动
jthread theThread(run, std::move(myPromise));
// 获取线程计算结果
int res = theFuture.get();
cout << "Res: " << res << '\n';
// 如果是普通thread类, 别忘了join操作
// theThread.join();
}
packaged_task
有了packaged_task
将可以更方便地使用promise
,而不是向上面那样要显式地使用promise
。具体使用方法详见下面的代码:
int run(int a, int b) { return a + b; }
int main()
{
// 创建一个packaged_task去跑run();
packaged_task<int(int, int)> task(run);
// 获取packaged_task的future
auto theFuture(task.get_future());
// 创建一个线程, 让它执行任务
thread theThread(std::move(task), 39, 3);
// 获取结果
int res = theFuture.get();
cout << "Res: " << res << '\n';
theThread.join();
return 0;
}
在上面的代码中,我们创建了一个packaged_task
去执行run()
。通过调用get_future()
,从packaged_task
中检索future
。启动线程,将packaged_task
移入其中(packaged_task
也无法被复制),等待结果并获取它。
通过使用packaged_task
,省去我们手动声明promise
并手动执行set_xxx()
,很方便。
async()
如果想让操作系统决定是否创建一个线程以进行某种计算,可使用std::async()
。它接收一个将要执行的函数,并返回可用于检索结果的future
。
async()
可通过两种方法运行函数:
- 创建一个新线程,异步运行提供的函数。
- 在返回的
future
上调用get()
方法时,在主线程上同步运行函数。
这两种方法通常是由操作系统选择的,当然也能手动指定策略参数:
launch::async
:强制使用方法1。launch::deferred
:强制使用方法2。launch::async | launch::deferred
:默认行为,让操作系统自由选择两种方法。
使用例如下:
int run() { return 123; }
int main()
{
auto myFuture(std::async(run));
int result = myFuture.get();
cout << result << '\n';
return 0;
}
需要注意的是,调用async()
锁返回的future
会在其析构函数中阻塞,直到结果可用为止。看看下列代码:
std::async(run);
这里返回了一个临时future
,将在该语句完成前就调用析构函数,在结果可用前,该析构函数将一直被阻塞。
使用future处理异常
可以用future
把子线程的异常放到主线程处理:
int run()
{
throw std::runtime_error("I am ERROR\n");
}
int main()
{
auto myFuture(std::async(std::launch::async, run));
try
{
int result = myFuture.get();
} catch (const std::exception& ex)
{
cout << ex.what();
}
return 0;
}
shared_future
future
的.get()
只能调用一次。如果想多次调用get()
,甚至从多个线程中多次调用,则需要使用std::shared_future<T>
,T
是可拷贝构造的。使用std::future::share()
或给shared_future
构造函数传递std::move(future)
以创建shared_future
。
shared_future
可用于同时唤醒多个线程,例如下列代码:
promise<void> thread1Started, thread2Started;
promise<int> signalPromise;
shared_future<int> signalFuture(signalPromise.get_future().share());
auto func1 = [&thread1Started, signalFuture]
{
thread1Started.set_value();
// 等待参数被设定
int parameter = signalFuture.get();
cout << "Func1: " << parameter << '\n';
};
auto func2 = [&thread2Started, signalFuture]
{
thread2Started.set_value();
// 等待参数被设定
int parameter = signalFuture.get();
cout << "Func2: " << parameter << '\n';
};
// 异步运行两个lambda表达式, 记得获取future结果
auto res1 = std::async(launch::async, func1);
auto res2 = std::async(launch::async, func2);
// 等待两个线程开始
thread1Started.get_future().wait();
thread2Started.get_future().wait();
// 两个线程等待参数被设定, 设定参数后两个都被唤醒
signalPromise.set_value(42);
在上述代码中,首先定义了两个lambda表达式,它们在不同线程上异步执行。每个lambda表达式首先将值设置为自己的promise,以指示线程已启动。然后对signalFuture
调用get()
,这将一直阻塞,直到可通过future获得参数为止。两个lambda表达式都有shared_future<int> signalFuture
的副本。主线程使用std::async()
在不同线程上执行这两个lambda表达式,一直等待两个线程启动后,设置signalFuture
的参数以唤醒这两个线程。
参考资料
- C++20高级编程