Modern C++ Concurrency Utilities 06: Promise, Future and Asynchronous
「期值」是标准库为异步任务中返回值以及异常的传递提供的基础设施. 具体的做法是将这些值关联到共享的「future」对象上. 异步任务可以向这些期值对象中写入返回值或异常值, 异步任务的发起线程则可以通过这些期值对象等待, 读取, 检查其所需要的异步结果. 标准库中为上述功能提供的设施即 std::promise
和 std::future
.
std::promise
promise
为异步任务提供了存储返回值或异常的机制, 可以说, 在「期值」构建的异步交互机制中, promise
对象及其接口负责 写入. 异步任务所存储的值可以被其他线程异步地通过该 promise
对象构建出的 std::future
对象获取.
从语义上看, 每个 std::promise
对象都表示一个「共享的状态」, 并且这个共享的状态可能还没有被计算和设置, 或者在计算时发生了异常. 异步程序可以对 std::promise
对象执行三种操作以设置其所代表的共享状态的状态:
- make ready: 将该
promise
对象所代表的共享状态设置为有效. 唤醒其他正在等待该共享状态的线程以读取该状态. - release: 将该
promise
对象和其关联的共享状态断开关联. - abandon: 设置该
promise
对象所代表的共享状态为异常状态, 然后 make ready 并 release
std::promise
提供的以下方法对应于上述三种抽象操作:
set_value
: 相当于 make ready, 向promise
对象所代表的共享状态设置值并标记为 ready.set_exception
: 相当于 abandon, 将promise
对象所代表的共享状态设置为异常状态并 make ready需要注意一个
promise
对象只能set_*
一次, 即如果已经调用了set_value
或set_exception
, 那么新的set_*
操作会抛出异常.get_future
: 获取一个与当前promise
对象关联的future
对象, 相当于获取当前promise
对象关联的共享状态的异步读取接口.需要注意的是一个
promise
只能get_future
一次, 如果多次get_future
会抛出异常.set_value_at_thread_exit
: 与set_value
不同之处在于该函数会设置promise
所代表的共享状态的值, 但是不会立即 make ready, 而是在当前线程退出时才 make readyset_exception_at_thread_exit
: 与set_value_at_thread_exit
同理
std::future
future
提供了「期值」体系中的异步读取接口. 对于通过 std::async
, std::packaged_task
等工具创建的异步任务, 或者手动使用 std::promise
的异步任务, 都提供了获取 future
对象的接口, 异步任务的创建者可以通过该接口获取 future
对象并读取结果, 或者在结果尚未计算完成并设置时等待结果, 或者在异步任务异常时捕获异常.
future
提供以下方法实现对异步结果的等待/验证/读取:
get
: 阻塞地等待future
对象所关联的共享状态被设置有效的值, 并且获取该值.valid
: 检查future
对象所关联的共享状态是否有效. 所谓「有效」指future
对象与一个共享状态相关联. 如果直接默认构造一个future
对象而不是从async
/packaged_task
/promise
获取future
对象, 则valid()
返回false
. 当对一个有效的future
对象调用get
后,valid
也返回false
.wait
: 阻塞等待到future
对象所关联的共享状态可被读取.wait_for
/wait_until
:wait
函数的 timeout 版本share
: 将当前future
对象关联的共享状态 转移 到一个std::shared_future
对象, 并返回该std::shared_future
对象.
std::shared_future
std::future
独占地指涉到共享状态 (不可拷贝), 不允许多个线程持有对一个共享状态的指涉, 因而无法做到多个线程访问该共享状态. std::shared_future
则可以做到多个 std::shared_future
对象指涉同一个共享状态 (可以拷贝). 同时多个线程通过不同的 std::shared_future
对象并发访问同一个共享状态是安全的.
std::shared_future
提供的方法在语义上只和 std::future
存在一点差别: 调用 get
后不再导致 valid
返回 false
.
std::async
std::async
函数模板接受可调用对象及其参数作为参数, 它会根据传入的执行策略参数 异步 地执行传入的可调用对象. std::async
会立即返回一个 std::future
对象, 作为读取传入的异步任务返回值的接口.
其中「执行策略」是一个枚举, 即 std::launch
. 标准定义了两种执行策略:
std::launch::async
: 异步执行传入的可调用对象, as if 在一个新的std::thread
对象中执行std::launch::deferred
: 将传入的可调用对象和其参数存储在返回的future
对象所指涉的共享状态中. 将其执行推迟到第一次对返回的future
对象调用 非 timeout 版本的wait
函数时. 后续对该future
对象的访问 (如get
) 将立即返回结果.
std::packaged_task
std::package_task
类模板提供一个 wrapper, 可以将任意的可调用对象 wrap 成异步任务 (可异步调用的可调用对象). 被 wrap 的异步任务的返回值或异常通过 future
对象传递. std::package_task
可以看作是 std::function
的异步版本.
valid
: 检查当前packaged_task
是否持有一个共享状态.get_future
: 返回一个指涉到当前packaged_task
所持有的共享状态的future
对象.operator()
: 调用其 wrap 的可调用对象, 返回值或异常将被储存到其持有的共享状态中.std::make_ready_at_thread_exit
: 执行其 wrap 的可调用对象, 并将返回值或异常储存到共享状态中, 但是并不立即将共享状态 make ready, 而是在当前线程退出时才 make ready.reset
: 重置内部状态, 抛弃之前的执行结果并在内部重建新的共享状态.