ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C++ std::promise深度解析

2026/10/7 15:10:05 拓冰建站 浏览量
C++ std::promise深度解析 std::promise是C11异步模型中的生产者端点与std::future共享状态实现线程间一次性结果传递。它通过set_value或set_exception写入结果或异常get_future()获取消费者视图get()阻塞等待结果。对象不可拷贝只能移动确保唯一所有权。适用于单次结果返回、跨线程异常传播及事件信号但不支持多生产/消费或频繁通信。其底层机制被std::async和std::packaged_task封装是手动控制异步结果的理想选择。一、状态机模型Producer Thread Shared State Consumer Thread ────────────── ───────────── ────────────── std::promise 值 / 异常 std::future set_value / set_exception ───▶ ready true ───▶ get / wait核心性质promise/future 是一对一次性通信通道共享状态可以持有一个T类型的值一个异常std::exception_ptr或者最终变成broken_promiseset_value/set_exception会使共享状态变为readyfuture::get()阻塞直到共享状态 ready并取走结果或重抛异常二、对象不可拷贝只能移动std::promiseint p; std::promiseint q p; // 编译错误拷贝构造被删除 std::promiseint r std::move(p); // OKp 此后无共享状态规则拷贝构造 / 拷贝赋值被删除移动构造 / 移动赋值转移共享状态所有权一个 promise 只能get_future()一次一个共享状态只能由一个 promise 写入三、核心成员函数3.1get_future()std::promiseint p; std::futureint f p.get_future();语义返回与当前 promise 共享同一共享状态的future第二次调用抛出std::future_error错误码future_already_retrievedpromise 被 move 走后原对象无共享状态调用会抛no_state顺序要求通常先get_future()再把 promise move 给工作线程。3.2set_valuep.set_value(42);语义将值写入共享状态原子地使共享状态变为 ready唤醒所有在future::get/wait上阻塞的线程约束只能调用一次再次调用抛std::future_error错误码promise_already_satisfied若 promise 无共享状态抛no_state值类型会按值/引用/右值规则拷贝或移动构造引用特化std::promiseconst std::string p; std::string s data; p.set_value(s); // 存的是引用调用方必须保证生命周期void特化std::promisevoid p; p.set_value(); // 仅作“事件已发生”的信号3.3set_exceptiontry { do_work(); p.set_value(result); } catch (...) { p.set_exception(std::current_exception()); }语义将std::exception_ptr存入共享状态共享状态变为 ready消费者调用future.get()时异常会在消费者线程被重新抛出这是 C 线程间安全传递异常的标准方式。不用 promise 时工作线程未捕获异常会直接std::terminate。3.4set_value_at_thread_exitp.set_value_at_thread_exit(42);语义立即把值放进共享状态但不马上置为 ready当前线程退出、所有线程局部对象析构完成后才把共享状态标记为 ready消费者会一直阻塞到生产者线程结束适用场景结果依赖线程局部存储TLS希望“线程真正收尾后再让别的线程看到结果”避免消费者拿到结果后生产者线程的 TLS 还没析构造成逻辑错误对应异常版本p.set_exception_at_thread_exit(std::current_exception());四、析构与 broken_promise如果 promise 被销毁而共享状态仍然没有被 set标准规定promise 放弃共享状态时会向共享状态写入std::future_error(std::future_errc::broken_promise)共享状态变为 ready消费者future.get()会抛出该异常而不是永久阻塞std::futureint f; { std::promiseint p; f p.get_future(); // p 析构但未 set_value } f.get(); // 抛 std::future_error(broken_promise)工程含义promise 的所有权必须交给“真正会兑现结果”的执行流。错误示例void worker(std::promiseint p) { // 如果某些分支没 set_valuep 析构时 broken_promise }更好void worker(std::promiseint p) { try { p.set_value(compute()); } catch (...) { p.set_exception(std::current_exception()); } }五、线程安全性标准保证对同一个共享状态的内部读写是同步的set_value与future::get之间存在 happens-before 关系set_value、set_exception、set_value_at_thread_exit、set_exception_at_thread_exit在更新 promise 对象时表现得像获取了与该 promise 关联的同一个互斥体但是多个线程同时调用同一个 promise 对象的set_value是未定义/非法用法。正确模型一个 promise单一生产者一个 future单一消费者多消费者请用std::shared_futurestd::promiseint p; std::shared_futureint sf p.get_future().share(); std::thread a([sf]{ std::cout sf.get(); }); std::thread b([sf]{ std::cout sf.get(); });六、与std::async/std::packaged_task的关系三者底层都是 promise/future工具谁创建 promise适用场景std::promise用户手动非函数式、事件驱动、手动通知std::packaged_task包装可调用对象把“函数对象”当任务投递给线程池std::async标准库内部一次性异步函数调用std::packaged_task本质struct packaged_task { promiseR p; F f; void operator()(Args... args) { try { p.set_value(f(args...)); } catch (...) { p.set_exception(std::current_exception()); } } };七、典型用法7.1 线程返回结果void worker(std::promiseint p) { p.set_value(heavy_compute()); } std::promiseint p; std::futureint f p.get_future(); std::thread t(worker, std::move(p)); int result f.get(); t.join();7.2 跨线程异常传播void worker(std::promisedouble p) { try { double v risky(); p.set_value(v); } catch (...) { p.set_exception(std::current_exception()); } } auto f p.get_future(); try { double v f.get(); } catch (const std::runtime_error e) { // 处理工作线程抛出的异常 }7.3 用voidpromise 做一次性信号std::promisevoid ready; std::futurevoid f ready.get_future(); std::thread producer([ready] { prepare_resources(); ready.set_value(); // 发信号资源准备好了 }); f.wait(); // 消费者等资源就绪 start_consuming();这比mutex condition_variable简洁但只能通知一次。八、常见错误8.1 多次 setp.set_value(1); p.set_value(2); // 抛 promise_already_satisfied8.2 promise 生命周期短于工作线程std::futureint f; std::thread t; { std::promiseint p; f p.get_future(); t std::thread([](std::promiseint p){ p.set_value(1); }, std::move(p)); } t.join();上面这段其实没问题因为 promise 已经 move 进线程了。真正危险的是std::promiseint p; auto f p.get_future(); std::thread([p]{ p.set_value(1); }).detach(); // 函数返回p 可能已析构线程还在跑 → UB / broken_promise规则promise 要么 move 进线程要么生命周期由智能指针/容器管理。8.3 把 promise 当队列用promise/future 是一次性的不适合多次生产/消费不适合事件流不适合管道消息需要这些能力时用std::queue mutex condition_variable无锁队列线程池任务队列九、适用边界适合一个任务产出一个结果工作线程不是简单函数调用而是事件/回调/状态机需要手动控制“什么时候兑现结果”需要跨线程传异常不适合高频小任务用线程池多生产者多消费者消息流用队列多次通知用 condition_variable / channel简单异步函数调用用std::async十、一句话总结std::promise是“我以后会给你一个结果”的契约对象set_value/set_exception是履约future.get()是等待履约不履约就是broken_promise。它是 C 并发模型里比std::thread抽象、比std::async底层的那一层当你需要“手动兑现结果”时用它当你只想要“函数异步返回值”时别直接用它。