如何在C++中实现一个简单的Futures/Promises库?

来源:Linux教程作者:俊华头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何在C++中实现一个简单的Futures/Promises库?》,敬请观看详情。异步任务执行后如何拿到结果而不阻塞线程?Futures与Promises通过共享状态解耦生产者与消费者。Promise负责写入值或异常,Future负责读取,二者引用同一原子状态机。相比裸线程加条件变量,该模型避免竞态且支持链式回调。本文用C++11标准组件构建最小可用库,覆盖状态转移、线程安全与值提取,帮助理解异步编程底层机制而非仅调用标准库。

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

如何在C++中实现一个简单的Futures/Promises库?

共享状态的设计与线程安全

Future和Promise之所以能跨线程协作,关键在于它们共享同一个状态对象。这个状态需要记录三种信息:当前是否已经有值、具体存储的值或异常、以及等待结果的线程同步原语。我们使用一个结构体SharedState来封装,并用std::mutexstd::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_valueset_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::futurestd::promise在接口上和我们的简易库相似,但标准实现做了更多优化,例如支持自定义分配器、与std::async集成、以及通过std::shared_future支持多消费者。我们手写的版本胜在透明,能看清条件变量与互斥量如何编排。

若想让库更实用,可以加入then方法实现链式调用,这就需要在SharedState里维护回调列表,一旦就绪就调度回调到指定线程。还可以支持wait_for超时等待,内部用条件变量的wait_for即可。另一种思路是用原子变量替代互斥量做无锁状态标记,但值存储仍需保证可见性,复杂度明显上升。

理解这些扩展方向后,你会发现在异步编程基础中,Futures/Promises只是一种约定:生产者承诺给值,消费者未来取值。无论语言标准库还是第三方框架,都围绕这个约定在性能、易用性、错误处理上做权衡。自己实现一遍,再去读标准库源码,障碍会小很多。

C++Futures_Promises异步编程修改时间:2026-08-15 06:27:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。