承接上一篇的条件变量,我们现在学习C++11并发库的最后一个板块future。前面 thread 没法直接拿到线程函数返回值,future 系列就是用来解决「子线程运算结果传递给主线程」。

1:前置说明

future 体系是 C++11 为解决 **"线程返回值传递" 和 "异步任务管理"** 设计的标准库,核心解决原生std::thread无法直接获取返回值、无法安全传递异常的痛点。

包含 5 个核心组件:

  • std::future<T>:结果消费者,读取异步任务的结果
  • std::promise<T>:结果生产者,手动写入异步任务的结果 / 异常
  • std::shared_future<T>:可共享的结果消费者,支持多线程同时读取
  • std::packaged_task<R(Args...)>:任务包装器,将任意可调用对象包装成异步任务
  • std::async:任务启动器,一键创建异步任务并返回 future

2:std::future<T>

1:总

std::future<T>是只读的结果句柄,用于获取异步任务的返回值或异常。他本身不存储数据,只是指向底层共享状态的指针。

2:核心API列表

接口 作用 参数 返回值 注意事项
future() noexcept 默认构造函数,创建一个无效的 future 调用任何成员函数都会抛异常
future(future&& other) noexcept 移动构造函数 other:要移动的 future 移动后other变为无效
future& operator=(future&& other) noexcept 移动赋值运算符 other:要移动的 future 自身引用 移动后other变为无效
T get() 阻塞等待结果就绪,然后返回结果 异步任务的返回值 1. 只能调用一次2. 结果未就绪会阻塞3. 任务抛异常会在这里重新抛出
void wait() const 只阻塞等待结果就绪,不取值 可以多次调用
template<class Rep, class Period><br>future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const 超时等待结果就绪 rel_time:等待的最长时长 future_status枚举:- ready:结果就绪- timeout:超时未就绪- deferred:任务是延迟执行的 可以多次调用
template<class Clock, class Duration><br>future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const 等待到指定时间点 abs_time:等待的截止时间 同上 可以多次调用
shared_future<T> share() noexcept 转换为shared_future 对应的shared_future对象 转换后原 future 变为无效
bool valid() const noexcept 检查 future 是否有效(是否指向共享状态) true有效,false无效 移动或调用get()后变为无效

3:常用示例

#include <future>
#include <iostream>
using namespace std;

int main() {
    // 假设已经通过promise/async获取了future<int> fu
    future<int> fu = async(launch::async, [](){ return 100; });

    // 检查是否有效
    if (fu.valid()) {
        // 超时等待1秒
        auto status = fu.wait_for(chrono::seconds(1));
        if (status == future_status::ready) {
            cout << "结果:" << fu.get() << endl; // 只能调用一次
        } else {
            cout << "等待超时" << endl;
        }
    }

    return 0;
}

4:坑

  • get()只能调用一次:第二次调用会抛出std::future_error异常
  • 无效 future 调用任何成员函数都会抛异常:移动、调用get()share()后,future 变为无效
  • wait()wait_for()不会消耗结果:可以多次调用,只有get()会消耗结果

3:std::promise<T>

1:总述

std::promise<T>可写的结果句柄,用于手动设置异步任务的返回值或异常。一个 promise 对应一个 future。

2:核心API列表

接口 作用 参数 返回值 注意事项
promise() 默认构造函数,创建一个新的 promise 自动创建底层共享状态
promise(promise&& other) noexcept 移动构造函数 other:要移动的 promise 移动后other变为无效
promise& operator=(promise&& other) noexcept 移动赋值运算符 other:要移动的 promise 自身引用 移动后other变为无效
future<T> get_future() 获取对应的 future 对象 与该 promise 关联的 future 1. 只能调用一次2. 调用后 promise 仍然有效
void set_value(const T& val) 设置正常返回值(左值) val:要设置的结果值 1. 只能调用一次2. 调用后结果变为就绪
void set_value(T&& val) 设置正常返回值(右值) val:要设置的结果值 同上,会移动构造结果
void set_exception(exception_ptr p) 设置异常 p:要设置的异常指针 1. 只能调用一次2. 调用后结果变为就绪
void set_value_at_thread_exit(const T& val) 线程退出时设置结果 val:要设置的结果值 线程完全退出后才会标记结果就绪
void set_exception_at_thread_exit(exception_ptr p) 线程退出时设置异常 p:要设置的异常指针 同上

3:常用示例

#include <future>
#include <thread>
using namespace std;

void worker(promise<int> pro) {
    try {
        // 模拟计算
        int result = 100 + 200;
        pro.set_value(result); // 设置正常结果
    } catch (...) {
        pro.set_exception(current_exception()); // 设置异常
    }
}

int main() {
    promise<int> pro;
    future<int> fu = pro.get_future(); // 获取对应的future

    thread t(worker, move(pro)); // promise不可拷贝,必须移动

    cout << fu.get() << endl; // 300

    t.join();
    return 0;
}

4:坑

  • promise 不可拷贝,只能移动:一个共享状态只能有一个生产者
  • get_future()只能调用一次:第二次调用会抛出std::future_error异常
  • set_value()set_exception()只能调用一次:重复调用会抛异常
  • promise 析构前未设置结果:future 调用get()时会抛出broken_promise异常

4:std::shared_future<T>

1:总述

std::shared_future<T>可共享的结果句柄,支持多个线程同时读取同一个异步任务的结果。可以从普通future转换而来。

2:核心API接口

std::future<T>几乎完全相同,只有以下区别:

  1. 支持拷贝构造和拷贝赋值:多个shared_future可以指向同一个共享状态
  2. get()可以多次调用:不会消耗结果,所有调用都返回相同的值
  3. 没有share()方法:本身就是共享的

3:常用示例

#include <future>
#include <thread>
#include <vector>
using namespace std;

int main() {
    promise<int> pro;
    shared_future<int> sfu = pro.get_future().share(); // 转换为shared_future

    vector<thread> threads;
    for (int i = 0; i < 3; ++i) {
        // 拷贝sfu,多个线程共享同一个结果
        threads.emplace_back([sfu]() {
            cout << "线程" << this_thread::get_id() << ":" << sfu.get() << endl;
        });
    }

    pro.set_value(42); // 设置结果,所有线程都能读到

    for (auto& t : threads) {
        t.join();
    }

    return 0;
}

4:坑

  1. get()返回的是 const 引用:不能修改结果值
  2. 所有shared_future都析构后,共享状态才会释放
  3. 线程安全:多个线程同时调用get()是安全的

5:std::pachaged_task<R (Args...)>

1:总述

std::packaged_task任务包装器,将任意可调用对象(函数、lambda、仿函数、类成员函数)包装成一个异步任务,自动绑定 promise 和 future,不用手动创建 promise。

2:核心API列表

接口 作用 参数 返回值 注意事项
packaged_task() 默认构造函数,创建一个无效的 packaged_task 调用任何成员函数都会抛异常
template<class F><br>explicit packaged_task(F&& f) 构造函数,包装可调用对象 f:要包装的可调用对象 要求f的签名与R(Args...)匹配
packaged_task(packaged_task&& other) noexcept 移动构造函数 other:要移动的 packaged_task 移动后other变为无效
packaged_task& operator=(packaged_task&& other) noexcept 移动赋值运算符 other:要移动的 packaged_task 自身引用 移动后other变为无效
future<R> get_future() 获取对应的 future 对象 与该任务关联的 future 1. 只能调用一次2. 调用后 packaged_task 仍然有效
void operator()(Args... args) 执行包装的任务 args:传递给任务的参数 1. 执行完成后自动设置结果2. 任务抛异常会自动设置到 future 中
void make_ready_at_thread_exit(Args... args) 线程退出时执行任务 args:传递给任务的参数 线程完全退出后才会标记结果就绪
void reset() 重置任务状态,可重新执行 1. 只能在任务执行完成后调用2. 会创建新的共享状态
bool valid() const noexcept 检查是否有效 true有效,false无效 移动或执行后仍然有效,除非被 reset

3:常用示例

#include <future>
#include <thread>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    // 包装函数,模板参数是函数签名
    packaged_task<int(int, int)> task(add);
    future<int> fu = task.get_future();

    // 启动线程执行任务,传递参数10和20
    thread t(move(task), 10, 20); // packaged_task不可拷贝,必须移动

    cout << fu.get() << endl; // 30

    t.join();
    return 0;
}

4:坑

  • packaged_task 不可拷贝,只能移动
  • get_future()只能调用一次
  • operator()只能调用一次:除非先调用reset()重置
  • 任务执行时自动处理异常:不需要手动调用set_exception()

6:std::async

1:总述

std::async最高频使用的接口,自动创建异步任务、自动管理线程、自动绑定 promise 和 future,一行代码就能启动异步任务。

2:核心API列表

// 1. 显式指定启动策略
template<class F, class... Args>
future<result_of_t<F(Args...)>> async(launch policy, F&& f, Args&&... args);

// 2. 默认启动策略(不推荐)
template<class F, class... Args>
future<result_of_t<F(Args...)>> async(F&& f, Args&&... args);

参数说明

  • policy:启动策略,是std::launch枚举类型,可选值:
    • std::launch::async强制新开一个线程执行任务
    • std::launch::deferred延迟执行,调用get()/wait()时才在当前线程执行
    • std::launch::async | std::launch::deferred:系统自选策略(默认值)
  • f:要执行的可调用对象
  • args:传递给可调用对象的参数

返回值:与任务返回值类型对应的future对象

3:常用示例

#include <future>
#include <iostream>
using namespace std;

int square(int x) {
    this_thread::sleep_for(chrono::seconds(1));
    return x * x;
}

int main() {
    // 显式指定新开线程执行(推荐)
    future<int> fu = async(launch::async, square, 10);

    cout << "主线程继续执行..." << endl;

    cout << "10的平方:" << fu.get() << endl; // 100

    return 0;
}

4:坑

  • 永远显式指定启动策略:默认策略行为不确定,不同编译器实现不同
  • 临时 future 会阻塞主线程async(launch::async, func)返回的临时 future 在析构时,会隐式调用join()等待线程结束
  • launch::deferred任务不调用get()永远不会执行
  • async 不保证使用线程池:GCC 和 Clang 的实现都是每次创建新线程,不是线程池
// 错误写法:主线程会阻塞1秒
async(launch::async, [](){ this_thread::sleep_for(chrono::seconds(1)); });
cout << "Hello" << endl; // 等上面执行完才打印

7:底层原理(浅显)

1:共享状态

所有 future 组件的底层都依赖共享状态(Shared State),这是一块堆上分配的内存块,是 promise 和 future 之间的通信桥梁。

共享状态里只存 3 样东西:

  1. 结果数据:异步任务的返回值或异常
  2. 同步原语:互斥锁 + 条件变量,实现线程安全的等待 - 通知
  3. 引用计数:原子计数器,管理共享状态的生命周期

2:四大组件和共享状态的关系

  • promise:持有共享状态的写权限,负责写入结果
  • future:持有共享状态的读权限,负责读取结果
  • packaged_task:内部包含一个 promise 和一个可调用对象,执行任务后自动写入结果
  • async:内部创建 packaged_task 和 thread,自动执行任务并返回 future

3:核心工作流程

  • 创建 promise/packaged_task 时,自动在堆上分配共享状态
  • future 通过get_future()获得共享状态的读权限
  • promise 调用set_value()或 packaged_task 执行完成时:
    • 将结果写入共享状态
    • 标记结果为 "就绪"
    • 唤醒所有在get()/wait()上阻塞的线程
  • future 调用get()时:
    • 如果结果未就绪,在条件变量上阻塞等待
    • 结果就绪后,读取结果并释放共享状态的读权限

4:关键机制

  1. 为什么get()会阻塞?:本质是在共享状态内部的条件变量上等待,直到结果就绪被唤醒
  2. 为什么get()只能调用一次?:普通 future 的get()会销毁共享状态中的结果,释放读权限
  3. 为什么 promise 必须移动?:一个共享状态只能有一个写者,避免多个线程同时写入结果
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐