C++ 标准库 future

下面对 C++11 起 <future> 头文件提供的异步编程与并发任务支持做一次系统、深入的梳理,包括核心类型、任务启动方式、结果获取与等待机制、异常传播,以及实践建议。


一、概述

  • <future> 主要用于异步任务的调度与结果获取,核心在于将“任务提交”与“结果消费”拆分为两个角色:
    • 发起者(Producer):将任务打包、提交,并最终“设置”结果或异常;
    • 消费者(Consumer):通过 std::future 或 std::shared_future 等对象等待任务完成并获取结果。
  • 它的设计遵循延迟执行同步等待异常传递三大原则,能与 std::thread、线程池、事件循环等底层机制配合使用。

二、核心类型与概念

类型说明
std::future<R>区分任务执行者和结果使用者,可一次性获取异步结果 R 或重新抛出异常。
std::promise<R>与 std::future<R> 配对,负责在任务结束后设置值或异常。
std::shared_future<R>可复制、可多次获取同一任务结果的未来对象。
std::packaged_task<R()>将可调用对象包装成可传递给线程或执行器的“任务”,并自动生成 future
std::async最简便的异步任务启动接口,返回 std::future<R>
std::launch启动策略枚举:async(新线程)/deferred(延迟到 get() 时执行)

三、std::future 与 std::promise

1. std::promise<R>

  • 功能:在某个线程或回调中,调用promise.set_value(val); // 或者 promise.set_exception(std::make_exception_ptr(ex)); 来传递计算结果或异常。
  • 与 std::future 配对std::promise<int> p; std::future<int> f = p.get_future(); // 子线程中执行 p.set_value(42); // 主线程中通过 f.get() 获取 42 并捕获异常

2. std::future<R>

  • 获取结果
    • R get():阻塞等待直到有值或异常,之后 一次性 获取并销毁共享状态;再次调用会抛 std::future_error.
    • void wait():仅等待完成,不取值。
    • wait_for(dur) / wait_until(tp):带超时等待,返回 std::future_status
  • 状态查询
    • valid():判断是否持有共享状态(对应 get_future() 或 async 返回的合法性)。
    • share():将 future 转为 shared_future

四、std::async 与启动策略

enum class launch { async = 1, deferred = 2 };
  • std::future<R> std::async(launch policy, F&& f, Args&&... args)
    • policy = std::launch::async:总是异步启动,新线程执行。
    • policy = std::launch::deferred:延迟启动,直到调用 future.get()wait() 时才在当前线程执行 f(args...)
    • policy 可按位或,若底层支持由实现决定策略。
  • 示例auto f1 = std::async(std::launch::async, []{ return compute(); }); auto f2 = std::async(std::launch::deferred, []{ return compute(); }); // f2 在此之前不会启动,即便是不同线程

五、std::packaged_task 与 std::shared_future

1. std::packaged_task<R()>

  • 功能:将任意可调用对象包装为可调用的任务,并内建一个与之配对的 std::future<R>
  • 用法std::packaged_task<int(int,int)> task([](int a,int b){ return a+b; }); std::future<int> f = task.get_future(); // 将 task 移入线程 std::thread t(std::move(task), 2, 3); int sum = f.get(); // 5 t.join();

2. std::shared_future<R>

  • 复制语义:多个消费者可同时 .get() 同一结果;适用于广播式通知。
  • 获取方式:由 std::future<R> 调用 share() 或直接由 std::promise 生成:auto sf = p.get_future().share();

六、错误与异常传播

  • 异常封装:若任务内部抛出未捕获异常,运行时会自动通过对应的 promise 或 async 捕获并储存为 exception_ptr,在 future.get() 时重新抛出。
  • 示例auto f = std::async([]{ throw std::runtime_error("error"); return 0; }); try { f.get(); } catch (const std::exception& e) { std::cerr << "Caught: " << e.what() << "\n"; }

七、常用接口汇总

接口说明
future.get()获取结果或抛出异常,一次性有效
future.wait()阻塞直到任务完成
future.wait_for(dur)等待指定时长,返回 std::future_status
future.wait_until(tp)等待至指定时刻
future.valid()判断是否持有共享状态
future.share()转为 shared_future
promise.set_value(v)设置正常结果
promise.set_exception(eptr)设置异常
packaged_task.get_future()获取与任务绑定的 future
async(launch, f, args...)按策略启动异步任务,返回 future

八、示例:多任务并行求和

#include <future>
#include <vector>
#include <numeric>

int parallel_sum(const std::vector<int>& v) {
    auto mid = v.size() / 2;
    // 左右两部分异步求和
    auto f1 = std::async(std::launch::async,
                         [&v,mid]{ return std::accumulate(v.begin(), v.begin()+mid, 0); });
    auto f2 = std::async(std::launch::async,
                         [&v,mid]{ return std::accumulate(v.begin()+mid, v.end(), 0); });
    return f1.get() + f2.get();
}

九、实践建议

  1. 控制并发粒度
    • 不要对每个小任务都用 async(async) 启动新线程,易导致过度线程上下文切换。可用线程池封装或限制并发度。
  2. 选择合适策略
    • 对开销大或 I/O 密集型任务用 std::launch::async;对于轻量短任务可考虑 deferred 或同步直接调用。
  3. 使用 shared_future
    • 当多个消费者都须等待同一结果时,用 shared_future 避免重复执行或转移所有权失败。
  4. 注意生命周期
    • std::packaged_task 与 std::promise 不可复制,需用 std::move 传递;析构前必须设置结果,否则调用 get() 会抛 future_error
  5. 及时调用 get()
    • 如果不需要结果也应调用 get() 或 wait(),否则底层资源(如线程)可能一直占用直到对象析构。
  6. 异常安全
    • 在使用 promise 时,若可能提前退出,要确保在所有路径中都调用 set_value 或 set_exception,否则对应的 get() 会挂起。
  7. 避免死锁
    • 在 future.get() 前不要持有可能与任务内部也要获取的锁,否则可能交叉等待。

通过以上对 <future> 中异步任务包装、结果获取、异常传播与实践要点的全面梳理,相信你能在并行计算、异步 I/O、任务依赖等场景中高效、稳健地使用标准库的异步支持。祝编码顺利!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注