在C++异步编程中,自己动手构造一个Futures/Promises库能够让人深入理解任务结果传递的本质。核心思路是让Promise对象在某个线程中生产结果,而Future对象在另一个线程中消费结果,两者通过一块受保护的共享状态通信。下面我们从基础结构开始,逐步搭建一个不依赖标准库future的最小实现。

共享状态的设计与线程安全
Future和Promise之所以能跨线程协作,关键在于它们共享同一个状态对象。这个状态需要记录三种信息:当前是否已经有值、具体存储的值或异常、以及等待结果的线程同步原语。我们使用一个结构体SharedState来封装,并用std::mutex与std::condition_variable保证多线程下写操作和读操作不会冲突。
当Promise调用set_value时,它必须先锁住互斥量,将值写入内部存储,再把状态标记为就绪,最后通知条件变量。Future在调用get时同样加锁,若状态未就绪就通过条件变量等待。这样的设计避免了忙等待,也防止了数据竞争。为了支持异常传递,状态里还要有一个std::exception_ptr字段,Promise可以通过set_exception填充它。
下面给出共享状态的核心定义。注意我们用enum class描述状态机,使代码更清晰,也方便后续扩展如"被取消"等状态。
#include <mutex>
#include <condition_variable>
#include <exception>
#include <utility>
template <typename T>
struct SharedState {
enum class Status { Pending, Ready, Exception };
Status status = Status::Pending;
T value;
std::exception_ptr exc;
std::mutex mtx;
std::condition_variable cv;
void set_value(T v) {
std::lock_guard<std::mutex> lock(mtx);
if (status != Status::Pending) return;
value = std::move(v);
status = Status::Ready;
cv.notify_all();
}
void set_exception(std::exception_ptr e) {
std::lock_guard<std::mutex> lock(mtx);
if (status != Status::Pending) return;
exc = e;
status = Status::Exception;
cv.notify_all();
}
T get() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this] { return status != Status::Pending; });
if (status == Status::Exception) {
std::rethrow_exception(exc);
}
return std::move(value);
}
};
Promise与Future类的对外接口
共享状态做好后,我们需要提供两个轻量句柄类。Promise持有指向SharedState的指针,负责写入;Future同样持有该指针,负责阻塞读取。两者通过同一个std::shared_ptr引用状态,当最后一个句柄析构时状态自动释放,不需要手动管理生命周期。
Promise的接口应至少包含set_value和set_exception,并且要防止重复设置。Future则提供get方法,在内部委托给共享状态的get。为了简洁,我们暂不支持then回调,但保留了扩展空间。下面的代码展示了这两个类的骨架。
需要特别注意的是,如果Promise被销毁而没有设置任何值,一个健壮的库应当让Future的get抛出"broken promise"异常。我们在析构函数里检查状态,若仍为Pending则注入一个标准异常,这样消费者不会永远等待。
template <typename T>
class Promise;
template <typename T>
class Future {
public:
Future(std::shared_ptr<SharedState<T>> s) : state(s) {}
T get() { return state->get(); }
private:
std::shared_ptr<SharedState<T>> state;
friend class Promise<T>;
};
template <typename T>
class Promise {
public:
Promise() : state(std::make_shared<SharedState<T>>()) {}
Future<T> get_future() { return Future<T>(state); }
void set_value(T v) { state->set_value(std::move(v)); }
void set_exception(std::exception_ptr e) { state->set_exception(e); }
~Promise() {
if (state && state->status == SharedState<T>::Status::Pending) {
state->set_exception(std::make_exception_ptr(
std::runtime_error("broken promise")));
}
}
private:
std::shared_ptr<SharedState<T>> state;
};
在异步任务中实际使用这个库
有了上述组件,我们就可以把耗时计算丢到后台线程,主线程通过Future拿结果。这种做法比直接join线程更灵活,因为结果的产生和消费完全解耦。下面示例模拟一个网络请求:工作线程睡眠两秒后通过Promise返回整数,主线程继续执行其他逻辑,之后再调用get提取。
在真实项目中,你可能把Promise放进线程池任务里,或者封装成async函数。我们的简易库没有处理线程池,但演示了核心契约:Promise和Future必须来自同一次创建,且Future的get只能调用一次(重复调用会触发未定义行为,可在SharedState里加标志禁止)。异常也会跨线程传播,例如工作线程抛错,主线程在get时重新抛出,保持了同步代码的错误处理习惯。
运行下面程序,控制台会先打印主线程消息,两秒后打印结果,证明异步通路正常。若把工作线程里的set_value换成set_exception,主线程将捕获到异常,验证异常通道可用。
#include <iostream>
#include <thread>
int main() {
Promise<int> p;
Future<int> f = p.get_future();
std::thread worker([p = std::move(p)]() mutable {
std::this_thread::sleep_for(std::chrono::seconds(2));
try {
int result = 42;
p.set_value(result);
} catch (...) {
p.set_exception(std::current_exception());
}
});
std::cout << "main continues without blockingn";
int val = f.get();
std::cout << "got value: " << val << "n";
worker.join();
return 0;
}
与标准库及进一步扩展的思考
C++11起提供的std::future和std::promise在接口上和我们的简易库相似,但标准实现做了更多优化,例如支持自定义分配器、与std::async集成、以及通过std::shared_future支持多消费者。我们手写的版本胜在透明,能看清条件变量与互斥量如何编排。
若想让库更实用,可以加入then方法实现链式调用,这就需要在SharedState里维护回调列表,一旦就绪就调度回调到指定线程。还可以支持wait_for超时等待,内部用条件变量的wait_for即可。另一种思路是用原子变量替代互斥量做无锁状态标记,但值存储仍需保证可见性,复杂度明显上升。
理解这些扩展方向后,你会发现在异步编程基础中,Futures/Promises只是一种约定:生产者承诺给值,消费者未来取值。无论语言标准库还是第三方框架,都围绕这个约定在性能、易用性、错误处理上做权衡。自己实现一遍,再去读标准库源码,障碍会小很多。
C++Futures_Promises异步编程修改时间:2026-08-15 06:27:32