C++单任务多Promise场景:多线程异步数据获取方案咨询
多线程共享异步数据请求的优化实现方案
你的场景很典型:多线程重复请求同一份key对应的数据,每个调用者需要拿到std::future自行等待结果,同时要避免对同一个key发起重复的服务端请求。你提到的“每个key维护promise队列”的方案是可行的,但确实有更简洁高效的优化思路——用std::shared_future替代promise队列,每个key仅需一个std::promise,所有请求共享它对应的shared_future。
核心思路
- 内部维护一个线程安全的映射表,记录每个key的请求状态:要么已完成(存储就绪的
shared_future),要么正在请求中(存储对应promise生成的shared_future)。 - 当线程调用
get_data时:- 如果key已有就绪的
shared_future,直接返回其拷贝; - 如果key正在请求中,返回当前的
shared_future; - 如果key还未发起请求,创建
promise、启动异步请求,并将promise的future转换为shared_future返回,同时记录到映射表中。
- 如果key已有就绪的
- 数据获取完成后,通过唯一的
promise设置结果,所有持有该shared_future的调用者都会被唤醒并拿到数据。
完整代码实现
#include <future> #include <map> #include <mutex> #include <memory> // 自定义的key和data类型,可根据实际业务调整 using key = int; struct data { // 示例字段,替换为实际数据结构 int value; }; class my_thousand_promises { public: std::future<data> get_data(key k) { std::lock_guard<std::mutex> lock(mtx_); auto iter = key_futures_.find(k); if (iter != key_futures_.end()) { // 已有请求(不管是处理中还是已完成),直接返回shared_future的拷贝 return iter->second; } // 首次请求,创建promise并启动异步任务 auto prom_ptr = std::make_shared<std::promise<data>>(); std::shared_future<data> shared_fut = prom_ptr->get_future().share(); // 记录到映射表,供后续请求复用 key_futures_.emplace(k, shared_fut); // 启动异步数据获取(实际场景替换为真实的服务端异步调用) std::async(std::launch::async, [this, k, prom_ptr]() { // 模拟服务端数据获取 data server_data = fetch_from_server(k); // 数据就绪后更新promise this->handle_data_received(k, std::move(server_data), prom_ptr); }); return shared_fut; } private: void handle_data_received(key k, data received_data, std::shared_ptr<std::promise<data>> prom_ptr) { // 设置promise结果,所有持有对应shared_future的线程都会被唤醒 prom_ptr->set_value(std::move(received_data)); // 可选:如果不需要长期缓存数据,可以在这里从key_futures_中删除该key // std::lock_guard<std::mutex> lock(mtx_); // key_futures_.erase(k); } // 模拟从服务端异步获取数据的函数 data fetch_from_server(key k) { // 实际逻辑:发起网络请求、等待响应等 return data{k * 10}; // 示例返回值 } std::mutex mtx_; // 存储每个key对应的shared_future,线程安全访问 std::map<key, std::shared_future<data>> key_futures_; };
方案优势
- 资源高效:每个key仅需一个
promise,避免维护大量promise队列,减少内存占用和操作开销。 - 逻辑简洁:
shared_future天然支持多线程共享,无需手动遍历队列兑现每个promise。 - 避免重复请求:同一个key只会发起一次服务端请求,节省服务端资源和网络带宽。
- 线程安全:通过全局
std::mutex保证映射表的并发访问安全(高并发场景可优化为分片锁进一步降低竞争)。
扩展注意事项
- 异常处理:如果服务端请求失败,可在
handle_data_received中调用prom_ptr->set_exception(),让所有future的调用方捕获异常。 - 数据缓存策略:如果数据需要过期失效,可以在映射表中存储包含过期时间的结构体,定期清理过期条目。
- 锁优化:当key数量极大且并发请求频繁时,可将映射表按key的哈希值分片,每个分片对应一个
mutex,减少锁竞争。
内容的提问来源于stack exchange,提问作者andreig1978
相关产品推荐
相关产品推荐

