标准库future分析

By Looning6 minutes read

Future/Promise 机制

根据维基百科,Future 与 Promise 是成对存在的,形成一种值于其运算过程相互关联的机制。Future 代表在未来交付的一个值,你可以利用获取的 Future 来查看这个值是否就绪,也可以阻塞等待这个值完成。而Promise则是设置这个值的对象,使用 Promise 设置这个值后,就能够通过 Future 获取这个结果。需要注意的是,Promise 只能设置一次这个结果。

目前有很多语言都实现和支持了 Future/Promise 机制,不过不同语言之间多少还是有些区别。闲话少说,先介绍 C++ 标准库中的实现。

C++ 中的 Future/Promise

c++ 在 11 版本中就引入了这个机制,相关接口如下:

下面简单介绍这些接口的用法和其内部实现。

promise

promise 的大致定义如下:

template<typename Res>
class promise {
    std::shared_ptr<State> future_;
    std::unique_ptr<Result<Res>> storage_; // 在源码中未发现其作用
}

为了便于理解,此处对代码做了部分简化,后续的代码展示也是如此。

future_ 是一个共享指针,指向 State 类型,这个类型用于描述当前计算的状态。promise 和其对应的 future 共享同一个 State。

关于 State 的简要定义如下:

class State {
    enum Status {not_ready, ready};
    std::unique_ptr<Result<Res>> result_; // 保存计算结果的指针
    std::atomic<Status> status_; // 表示计算结果是否完成
    std::atomic_flag retrieved_; // 条件变量
    std::once_flag once_; // 用来确保多线程并发时只有一个线程会设置 result_,且单线程多次调用也只会运行一次
}

Result是一个包装器,其内部包含Res需要的内存空间和一个用来表示是否初始化的布尔值。使用这个是防止在计算完成前需要使用默认构造函数来初始化对应的ResRes没有默认构造函数,并在出现异常时保存相关的异常指针。

成员函数

get_future的实现比较简单,直接把共享指针传入 Future 类中构造一个新 Future 实例即可。

std::future<R> get_future() {
    return Future<R>(future_);
}

set_value则是设置对应的State::result_,并利用State::once_来实现一个 promise 只会设置一次 value 的目的。

set_value_at_thread_exitset_value类似,但是设置的时机不是调用函数时,而是在线程退出时。 其实现机制大概是利用一个thread_localRAII实例保存这些变量,然后在线程退出要析构这些实例时便能够通过析构函数设置对应的 value。

set_exceptionset_value类似,但是不是设置对应的 value,而是设置异常。

set_exception_at_thread_exit同上类似。

packaged_task

packaged_task的实现与promise类似,不过其内部成员不是State,而是TaskState

class packaged_task {
    std::shared_ptr<TaskState> state_;
}

class TaskState: State {
    _Fn impl_;
}

packaged_taskpromise不同之处在于,前者是一个可调用对象,其目的是封装一个可调用对象,然后进行异步调用,并将结果通过future返回。

成员函数

valid用于判断是否拥有一个共享状态。

get_future获得当前共享状态对应的future

operator()执行异步调用。

make_ready_at_thread_exit执行异步调用,并在当前线程退出后才会设置共享状态为 ready。

reset重置当前可调用对象和共享状态。

future

future是在State的基础上封装了一些跟 future 语义相关的接口而形成的类。其定义如下:

class future {
    std::shared_ptr<State> state_;
}

成员函数

share用于将不可拷贝的future切换成共享状态的shared_future

get获取 future 对应的值,如果该值尚未就绪,则会利用State::retrieved_条件变量阻塞等待。异步运行完成后会唤醒当前阻塞的线程。

valid判断当前计算是否已完成。

wait阻塞等待计算完成。

wait_for阻塞等待一段时间,函数会返回一个future_status枚举实例来说明等待结果。

wait_until阻塞等待直到某个时间,函数会返回一个future_status枚举实例来说明等待结果。

shared_future

future类似,但允许多个线程共享异步结果。

async

传入一个异步调用函数,根据launch选择异步调用策略,并返回一个future作为异步结果。

launch

决定async的异步调用策略。

enum class launch  {
    async // 启动新线程执行当前任务
    deferred // 任务在请求其结果时在请求的线程上执行(惰性求值)
};

future_status

用于说明在调用wait_forwait_until后的future状态。

enum class future_status {
    ready, // 结果已就绪
    timeout, // 等待超时
    deferred // 共享状态包含推迟策略,需要明确请求才会进行计算
};

至此,我们完成了关于 c++ 标准库中 future 组件的实现的简要分析。