C++11引入了std::future和std::promise,为异步编程提供了更结构化的工具。

  • 通过std::async或std::packaged_task启动异步任务。
  • 通过std::promise则用于设置异步操作的结果。
  • 通过std::future获取异步操作的结果。

异步任务的结果

future

std::future是C++的一种模板类,它用于表示异步操作的结果。可用于获取异步任务的返回值或者等待异步任务完成

一个有效的std::future对象通常由以下三种Provider创建,并和某个共享状态相关联:

  • std::async()
  • std::packaged_task::get_future()
  • std::promise::get_future()

共享状态

共享状态(shared state)是一个内部容器,用于管理异步操作的状态,包括结果值、异常信息等。这个容器在两个或多个线程之间共享。std::future内部持有指向这个共享状态的指针,可以理解是共享状态对象的接口或句柄。

  • 共享状态对象保存线程函数及其参数、返回值以及新线程状态等信息。该对象通常创建在堆上,由Provider提供,并交由future管理。
  • Provider将计算结果写入共享状态对象,而future通过get()函数来读取该结果。
  • 共享状态对象作为异步结果的传输通道,future可以从中方便地获取线程函数的返回值。
  • 共享状态内部保存着一个引用计数,当引用计数为0时共享状态才会被释放。

构造函数

  • 支持无参默认构造,此对象没有共享状态,因此它是无效的,但是可以通过移动赋值的方式将一个有效的future值赋值给它。
  • 支持移动构造。
  • 不支持拷贝构造

get()

get()函数的三个版本:

T get();

T& get();

void get();

get()用于获取异步操作的结果:

  • 当共享状态就绪时,返回存储在共享状态中的值(或抛出异常)。
  • 如果共享状态尚未就绪(即Provider尚未设置其值或异常),则该函数将阻塞调用线程直到就绪。
  • 当共享状态就绪后,则该函数将解除阻塞并返回(或抛出异常),释放其共享状态,这使得future对象不再有效,因此对于每一个future的共享状态,该函数最多应被调用一次。
  • void get()不返回任何值,但仍等待共享状态就绪并释放它。

get()函数是通过移动语义将异步结果从future中转移给get的返回值,因此该函数只能被调用一次,同时也意味着这个future对象也不可再使用(valid()为false)。

可以通过查询future的状态:

  • ready:
  • timeout:
  • deferred:

wait()

wait()函数等待共享状态就绪,但不获取异步操作的结果。

  1. 等待共享状态就绪。
  2. 如果共享状态尚未就绪,则该函数将阻塞调用的线程直到就绪。
  3. 当共享状态就绪后,则该函数将解除阻塞并返回。

wait()函数不改变future对象的共享状态。

除了wait()外,wait_for()和 wait_until()函数用于带超时的等待,返回值:

  • std::future_status::ready: 共享状态已准备好,异步操作已完成。
  • std::future_status::timeout: 在指定的时间内,共享状态没有准备好。
  • std::future_status::deferred: 任务被延迟执行。

valid()

valid()检查共享状态的有效性,返回当前的future对象是否与共享状态关联。

share()

share()函数用于将一个独占所有权的std::future转换为一个可以共享的std::shared_future。

当调用future的share()函数时,将创建一个shared_future对象,同时原来的future将失去对共享状态对象的所有权,future内部持有的对共享状态的所有权被移动(move)到新创建的std::shared_future对象中。此后future对象处于无效状态,不能被使用(其valid()为false)。

shared_future

std::shared_future同样用于表示异步操作的结果。与std::future的区别:

  • std::future独享共享状态的所有权,而std::shared_future则共享所有权。
  • std::future是只移动类型,而std::shared_future既可移动也可复制。
  • std::future的get()函数只能调用一次,而std::shared_future的get()可以多次被调用。

共享状态对象内部维护着一个引用计数器。当调用share()创建std::shared_future对象时,该共享状态的引用计数为1,当复制一个std::shared_future对象时,引用计数加一,当析构一个std::shared_future对象时,引用计数减一,当引用计数变为0时,该共享状态对象会被自动释放。

特性 std::future std::shared_future
典型场景 一个生产者线程和一个消费者线程。当生产者完成任务后,只有一个消费者需要获取结果。 一个生产者线程和多个消费者线程。多个消费者(可能在不同线程上)都需要获取同一个结果。
get() 调用 只能调用一次。 可以调用多次。
生命周期 get() 被调用且共享状态被访问后,对象通常会失效。 引用计数管理。只有当所有关联的 std::shared_future 副本都被销毁后,共享状态才会被释放。
创建方式 通常由 std::asyncstd::promise::get_futurestd::packaged_task::get_future 返回。 必须通过 std::future::share() 方法从 std::future 转换而来。

创建异步任务

async

async的两个版本:

std::future<> async( F&& f, Args&&... args );

std::future<> async(std::launch policy, F&& f, Args&&... args );

policy表示启动策略:

  • std::launch::async: 确保函数在新的线程中执行。
  • std::launch::deferred: 函数的执行被延迟,直到调用 future 对象的 get()wait() 方法才开始执行,并且调用 get()wait() 的线程会被阻塞,直到函数执行完成。
  • std::launch::async | std::launch::deferred: 让系统决定是否启动新线程,这是默认行为

packaged_task

std::packaged_task将任何可调用对象封装成一个task,使得能异步调用它,其返回值或所抛异常被存储于能通过std::future对象访问的共享状态中。简言之,std::packaged_task将一个普通的可调用函数对象转换为异步执行的任务。

std::packaged_task不会自己启动,你必须调用它。

构造函数

  • 支持无参构造,创建一个空的packaged_task对象,无共享状态。
  • 支持可调用对象的构造,将一个可调用对象封装成一个packaged_task对象。
  • 支持移动构造,不支持拷贝构造。
  • 支持移动赋值,不支持拷贝赋值。

成员函数

  1. get_future():
    • 返回一个与packaged_task对象的共享状态关联的std::future对象。
    • 对于每一个 packaged_task,只能调用一次。。
  2. operator():
    • 调用该packaged_task对象所包装的可调用对象
    • 返回值被保存在与该packaged_task关联的的共享状态中。
  3. make_ready_at_thread_exit():调用该packaged_task对象所包装的可调用对象, 但是并不会立即设置共享状态的标志为ready,而是在线程退出时才设置共享状态的标志为ready。
  4. reset(): 重置 packaged_task 的共享状态,但是保留之前的被包装的任务。
  5. valid():检查packaged_task对象是否具有共享状态。

设置异步结果

std::promise用来实现线程间的异步通信。promise提供了一个承诺(promise),表示在某个时间点一定会有一个值或一个异常被设置

promise在一个线程中设置一个值,而另一个线程中可以通过std::future来获取这个值。通常的做法是:

  1. 创建一个promise对象。
  2. 通过promise对象获取一个future对象。
  3. 在一个线程中将值或异常设置到promise对象中
  4. 在另外一个线程通过future对象来获取值或异常。

std::promise对象只能使用一次。

构造函数

  • 只有一个无参默认构造函数。
  • 支持移动构造,不支持拷贝构造。
// 承诺提供一个int类型的值
std::promise<int> int_promise;

// 不返回任何值,只表示操作完成
std::promise<void> void_promise;

成员函数

get_future:返回与之关联的 future 对象,只能调用一次。

set_value:原子的设置值,并使 future 进入就绪状态。

set_value_at_thread_exit:原子的设置值,只有在调用该方法的线程结束并且所有线程局部对象被销毁后,future 才会进入就绪状态。

set_exception:设置异常,并使 future 进入就绪状态。

set_exception_at_thread_exit:设置异常,只有在调用该方法的线程结束并且所有线程局部对象被销毁后,future 才会进入就绪状态。

参考