文章目录
- [std::promise 详解及底层原理](#std::promise 详解及底层原理)
-
- [一、std::promise 核心定位与作用](#一、std::promise 核心定位与作用)
- [二、核心设计:共享状态(Shared State)](#二、核心设计:共享状态(Shared State))
- [三、promise 与 future 的协作流程](#三、promise 与 future 的协作流程)
- [四、promise 的模板特化](#四、promise 的模板特化)
-
- [1. 主模板:template <class T> promise<T>](#1. 主模板:template <class T> promise<T>)
- [2. 引用特化:template <class R> promise<R&>](#2. 引用特化:template <class R> promise<R&>)
- [3. void 特化:template <> promise<void>](#3. void 特化:template <> promise<void>)
- [五、promise 的核心成员函数](#五、promise 的核心成员函数)
-
- [1. 关联 future:get_future()](#1. 关联 future:get_future())
- [2. 设置结果(核心生产操作)](#2. 设置结果(核心生产操作))
- [3. 辅助操作](#3. 辅助操作)
- 六、关键特性总结
- 七、经典使用示例(含值传递+异常传递)
-
- [1. 基础值传递(对应开篇示例)](#1. 基础值传递(对应开篇示例))
- [2. 跨线程异常传递](#2. 跨线程异常传递)
- 八、底层原理补充:阻塞与唤醒的实现
- 核心总结
std::promise 详解及底层原理
一、std::promise 核心定位与作用
std::promise 是 C++11 引入的异步结果提供者 (异步 provider),定义在 <future> 头文件中,核心作用是在一个线程中存储值(类型为 T)或异常 ,并将该结果关联到对应的 std::future 对象,让另一个线程 可以通过 std::future 安全地获取这个结果(值/异常),是实现线程间同步与结果传递的核心组件。
简单来说:promise 负责「生产结果」,future 负责「消费结果」,二者配对使用,完成跨线程的结果传递和同步。
二、核心设计:共享状态(Shared State)
std::promise 和 std::future 的底层通信依赖共享状态(Shared State),这是一个线程安全的、堆上分配的内部对象,也是二者协作的核心载体,其核心特性如下:
- 关联关系 :
promise构造时会创建一个新的共享状态,调用promise::get_future()后,future会与该共享状态绑定,二者共享同一个状态; - 状态标识:共享状态包含「未就绪(not ready)」「就绪(ready,存储值)」「就绪(ready,存储异常)」三种核心状态,初始为「未就绪」;
- 线程安全:共享状态的所有操作(设置值/异常、获取值/异常、状态判断)都是线程安全的,无需用户额外加锁;
- 生命周期 :共享状态的生命周期独立于
promise和future,会持续到最后一个关联的对象(promise/future/shared_future)被销毁或显式释放 ,即使创建它的promise先被销毁,只要有future关联,共享状态就会存活。
共享状态的核心数据
共享状态内部至少包含以下关键数据:
- 结果存储区:用于保存
promise设置的值(类型 T) 或异常(std::exception_ptr 类型); - 状态标志位:标记当前是「未就绪」「值就绪」还是「异常就绪」;
- 等待队列:存储阻塞在该共享状态上的线程(用于
future::get()/wait()等操作的阻塞与唤醒); - 引用计数器:统计关联的
promise/future数量,用于管理生命周期。
三、promise 与 future 的协作流程
二者的协作是典型的「生产者-消费者」模型,promise 是生产者,future 是消费者,核心流程基于共享状态的状态转换,步骤如下:
- 创建共享状态 :主线程创建
std::promise<T>对象,此时自动创建一个「未就绪」状态的共享状态; - 绑定 future :调用
promise.get_future(),得到std::future<T>对象,该future与上述共享状态绑定; - 传递 future :将
future对象传递给工作线程(可通过值传递/引用传递,推荐配合std::ref避免拷贝),promise保留在生产者线程(主线程/其他工作线程); - 消费者阻塞等待 :工作线程调用
future::get()或future::wait(),此时检测到共享状态为「未就绪」,工作线程会被阻塞,并加入共享状态的等待队列; - 生产者设置结果 :生产者线程调用
promise::set_value(T)(设置值)或promise::set_exception()(设置异常),将结果写入共享状态,并将共享状态的标志位设置为「就绪」; - 唤醒阻塞线程 :共享状态变为「就绪」后,会自动唤醒所有阻塞在该状态上的线程(工作线程),并将等待队列清空;
- 消费者获取结果 :被唤醒的工作线程从共享状态中读取值(
future::get())或异常(若设置了异常,get()会重新抛出该异常),完成结果消费; - 释放共享状态 :当
promise和future都被销毁(最后一个关联对象),共享状态的引用计数器归 0,被自动销毁。
关键注意点
future::get()是一次性操作 :一旦调用get()获取到结果(值/异常),共享状态的结果会被转移(或拷贝,视类型而定),后续再次调用get()会触发未定义行为(可使用std::shared_future支持多次获取);- 若
promise未设置值/异常就被销毁,共享状态会被标记为「异常就绪」,此时future::get()会抛出std::future_error异常(错误码:std::future_errc::broken_promise)。
四、promise 的模板特化
std::promise 提供了三个版本 的实现(主模板+两个特化),以支持不同的结果类型,特化的核心目的是适配「引用类型」和「无返回值(void)」的场景,仅对 set_value/set_value_at_thread_exit 的参数做调整,其余功能(如共享状态、与 future 协作、异常处理)与主模板完全一致:
1. 主模板:template promise
适配普通值类型(如 int/std::string/自定义类型),set_value() 需传入T 类型的对象(值传递/移动传递),示例:
cpp
std::promise<int> prom;
prom.set_value(10); // 传入int类型值,写入共享状态
2. 引用特化:template promise<R&>
适配引用类型(如 int&/std::string&),set_value() 需传入R 类型的左值(而非 R&),用于将共享状态绑定到一个已存在的对象,示例:
cpp
int x = 20;
std::promise<int&> prom;
prom.set_value(x); // 传入int左值,共享状态存储该对象的引用
std::future<int&> fut = prom.get_future();
int& ref = fut.get(); // ref 是 x 的引用,修改 ref 即修改 x
3. void 特化:template <> promise
适配「无返回值」场景,set_value() 无参数,仅用于将共享状态标记为「就绪」,实现纯同步(无需传递值,仅通知消费者任务完成),示例:
cpp
std::promise<void> prom;
std::future<void> fut = prom.get_future();
// 工作线程等待:仅等待状态就绪,无值可获取
std::thread th([&fut]() { fut.wait(); std::cout << "任务完成!\n"; });
prom.set_value(); // 无参数,仅将共享状态置为就绪,唤醒工作线程
th.join();
五、promise 的核心成员函数
promise 的所有成员函数都是围绕操作共享状态设计的,核心函数可分为「关联 future」「设置结果」「辅助操作」三类,均为公有成员函数,关键函数说明如下:
1. 关联 future:get_future()
- 功能:返回一个与当前
promise共享状态绑定的std::future<T>对象; - 注意:一个 promise 只能调用一次 get_future() ,多次调用会抛出
std::future_error异常(错误码:std::future_errc::future_already_retrieved)。
2. 设置结果(核心生产操作)
这类函数的核心作用是修改共享状态:将值/异常写入共享状态,并将状态置为「就绪」,唤醒阻塞线程,分为「立即生效」和「线程退出时生效」两类:
| 函数 | 功能 | 注意 |
|---|---|---|
set_value(const T& val)/set_value(T&& val) |
立即将值 val 写入共享状态,置为「值就绪」 | 仅能调用一次,多次调用抛异常;状态已就绪时调用也抛异常 |
set_exception(std::exception_ptr e) |
立即将异常 e 写入共享状态,置为「异常就绪」 | 需通过 std::make_exception_ptr() 创建异常指针,future::get() 会重新抛出该异常 |
set_value_at_thread_exit(T val) |
暂存值 val,当前线程退出时才将值写入共享状态并置为就绪 | 线程退出前共享状态仍为未就绪,适合需要线程资源清理完成后再通知的场景 |
set_exception_at_thread_exit(std::exception_ptr e) |
暂存异常 e,当前线程退出时才将异常写入共享状态并置为就绪 | 与 set_value_at_thread_exit 配合,保证线程资源释放后再传递异常 |
3. 辅助操作
swap(promise& other):交换两个promise的共享状态,无拷贝,仅交换内部指针,效率极高;- 移动构造/移动赋值(
promise(promise&&)/promise& operator=(promise&&)):promise是不可拷贝 的(拷贝构造/拷贝赋值被删除),仅支持移动,移动后原promise不再关联任何共享状态; - 析构函数:销毁
promise,若其仍关联共享状态且状态为「未就绪」,则将共享状态标记为「异常就绪」(抛出broken_promise异常),并减少共享状态的引用计数。
六、关键特性总结
- 不可拷贝,仅可移动 :
promise的拷贝构造和拷贝赋值被显式删除,只能通过移动语义转移所有权,避免多个promise操作同一个共享状态导致的冲突; - 结果唯一性 :一个
promise只能设置一次 值或异常,多次调用set_value()/set_exception()会抛出std::future_error异常; - 线程安全 :
promise的所有成员函数都是线程安全的,多个线程可同时操作一个promise(但实际场景中通常一个promise由一个生产者线程操作); - 异常传递 :支持通过
std::exception_ptr跨线程传递异常,future::get()会自动重新抛出该异常,实现异常的跨线程传播; - 纯同步支持 :通过
promise<void>可实现无结果的纯线程同步,仅通过共享状态的就绪状态实现阻塞与唤醒。
七、经典使用示例(含值传递+异常传递)
1. 基础值传递(对应开篇示例)
cpp
#include <iostream>
#include <thread>
#include <future>
#include <functional>
// 消费者线程:通过future获取值
void consumer(std::future<int>& fut) {
try {
int val = fut.get(); // 阻塞等待,直到共享状态就绪
std::cout << "消费者获取到值:" << val << std::endl;
} catch (const std::exception& e) {
std::cout << "消费者捕获异常:" << e.what() << std::endl;
}
}
int main() {
std::promise<int> prom; // 创建promise,生成共享状态
std::future<int> fut = prom.get_future(); // 绑定future
// 启动消费者线程,传递future(std::ref避免拷贝)
std::thread th(consumer, std::ref(fut));
// 生产者设置值,将共享状态置为就绪,唤醒消费者
prom.set_value(100);
th.join();
return 0;
}
// 输出:消费者获取到值:100
2. 跨线程异常传递
cpp
#include <iostream>
#include <thread>
#include <future>
#include <functional>
#include <stdexcept>
void consumer(std::future<int>& fut) {
try {
int val = fut.get();
std::cout << "消费者获取到值:" << val << std::endl;
} catch (const std::runtime_error& e) {
std::cout << "消费者捕获运行时异常:" << e.what() << std::endl;
} catch (const std::future_error& e) {
std::cout << "消费者捕获future异常:" << e.what() << std::endl;
}
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread th(consumer, std::ref(fut));
// 生产者设置异常,替代值传递
prom.set_exception(std::make_exception_ptr(std::runtime_error("计算失败:除数为0")));
th.join();
return 0;
}
// 输出:消费者捕获运行时异常:计算失败:除数为0
八、底层原理补充:阻塞与唤醒的实现
共享状态的「阻塞-唤醒」是实现 future::get() 阻塞等待的核心,其底层依赖操作系统的同步原语 (如 POSIX 下的 pthread_cond_t 条件变量+pthread_mutex_t 互斥锁,Windows 下的 CONDITION_VARIABLE+CRITICAL_SECTION),核心逻辑如下:
- 当线程调用
future::get()时,先加锁保护共享状态,检测状态是否为「就绪」; - 若未就绪,将当前线程加入共享状态的等待队列,然后解锁并阻塞(调用条件变量的
wait()方法,原子性完成「解锁+阻塞」,避免竞态); - 当
promise设置值/异常后,加锁修改共享状态为「就绪」,然后通过条件变量唤醒等待队列中的所有线程; - 被唤醒的线程重新加锁,检测共享状态是否就绪,确认后解锁并读取结果,完成操作。
该实现保证了「无忙等」,阻塞的线程不会占用 CPU 资源,是高效的线程同步方式。
核心总结
std::promise是异步结果生产者 ,与std::future配对实现跨线程的结果传递和同步,核心依赖共享状态;- 共享状态是线程安全的堆对象,独立管理生命周期,是
promise和future的通信载体,存储结果(值/异常)和状态; promise提供三个版本(主模板+R&/void特化),仅适配结果类型,底层逻辑一致,支持值、引用、无返回值三种场景;- 核心协作流程:
promise创建共享状态 →future绑定状态 → 消费者阻塞 → 生产者设置结果(置就绪+唤醒)→ 消费者获取结果; - 关键特性:不可拷贝仅可移动、结果唯一、线程安全、支持异常跨线程传递,是 C++ 异步编程的基础组件。