C++异步P2P请求中重复锁Mutex的优化方案咨询
问题描述
我正在用C++和websockets开发一个P2P库,目前实现向节点发送异步请求的功能,现有Request类如下:
class Request { private: CommandType _command; // The command type. std::string _id; // The request id. std::string _nodeId; // The host node id. Message _answer; // The answer to the request. std::mutex _mutex; // Mutex for waiting for the request. bool _isAnswered = false; void release() { _mutex.unlock(); }; public: Request(const CommandType& command, const std::string& id, const std::string& nodeId) : _command(command), _id(id), _nodeId(nodeId) {}; const CommandType command() const { return _command; }; const std::string_view id() const { return _id; }; const std::string_view nodeId() const { return _nodeId; }; const Message& answer() const { return _answer; }; const bool isAnswered() const { return _isAnswered; }; void setAnswer(const Message& answer) { _answer = answer; _isAnswered = true; std::cout << "release..." << std::endl; release(); }; void wait() { if (!_isAnswered) { _mutex.lock(); _mutex.lock(); _mutex.unlock(); } }; };
Manager类持有std::unordered_map<std::string, std::unique_ptr<Request>>(键为Request::id()),并提供makeRequest函数负责在映射中创建Request并返回其引用。makeRequest返回前会调用sendToNode异步发送请求(请求字符串不存储在Request类中,仅存储响应),之后返回指针引用,调用方会调用该指针的wait()方法。后续websocket从节点异步读取消息时,会调用Manager的handleMessage,解析请求响应的ID,检查该ID是否存在于无序映射中,若存在则调用setAnswer以解锁wait中的双重锁。
我认为wait函数中两次锁mutex的实现方式非常糟糕,请问有什么更好的替代方案?
替代方案
方案一:使用std::condition_variable
这是C++标准库中专门用于线程同步的工具,比手动锁解锁逻辑更安全、可读性更强。修改后的Request类如下:
#include <condition_variable> class Request { private: CommandType _command; std::string _id; std::string _nodeId; Message _answer; std::mutex _mutex; std::condition_variable _cv; bool _isAnswered = false; public: Request(const CommandType& command, const std::string& id, const std::string& nodeId) : _command(command), _id(id), _nodeId(nodeId) {}; const CommandType command() const { return _command; }; const std::string_view id() const { return _id; }; const std::string_view nodeId() const { return _nodeId; }; const Message& answer() const { return _answer; }; const bool isAnswered() const { return _isAnswered; }; void setAnswer(const Message& answer) { std::lock_guard<std::mutex> lock(_mutex); _answer = answer; _isAnswered = true; _cv.notify_one(); // 唤醒等待响应的线程 }; void wait() { std::unique_lock<std::mutex> lock(_mutex); // 等待直到_isAnswered为true,自动处理虚假唤醒问题 _cv.wait(lock, [this] { return _isAnswered; }); }; };
核心说明:
std::condition_variable配合std::unique_lock使用,wait方法会自动释放锁并阻塞线程,直到被notify_one唤醒;唤醒后会重新检查条件(_isAnswered是否为true),彻底避免虚假唤醒风险。setAnswer中用std::lock_guard确保修改状态时的线程安全,之后通过notify_one精准唤醒等待的线程。- 完全符合C++线程同步的最佳实践,逻辑清晰且无手动锁解锁的潜在错误。
方案二:使用std::promise和std::future
如果不需要直接暴露_isAnswered这类状态,可以用std::promise/std::future组合封装结果传递逻辑,让调用方通过future自然等待响应。修改后的Request类如下:
#include <future> class Request { private: CommandType _command; std::string _id; std::string _nodeId; std::promise<Message> _promise; std::shared_future<Message> _future; // 支持多线程同时等待同一个请求的响应 public: Request(const CommandType& command, const std::string& id, const std::string& nodeId) : _command(command), _id(id), _nodeId(nodeId), _future(_promise.get_future()) {}; const CommandType command() const { return _command; }; const std::string_view id() const { return _id; }; const std::string_view nodeId() const { return _nodeId; }; // 让调用方获取future,自主控制等待逻辑 std::shared_future<Message> getFuture() const { return _future; }; void setAnswer(const Message& answer) { _promise.set_value(answer); // 设置结果,自动唤醒所有等待的future }; // 兼容原有的wait接口 void wait() { _future.wait(); }; // 获取响应(需确保已调用wait或future已就绪) const Message& answer() const { return _future.get(); }; };
核心说明:
std::promise负责设置响应结果,std::shared_future(而非普通future)支持多个线程同时等待同一个请求的响应。setAnswer调用set_value后,所有等待该future的线程会被自动唤醒,future.get()直接返回已设置的Message。- 完全委托标准库处理同步逻辑,无需手动管理锁和条件变量,代码更简洁,出错概率更低。
内容的提问来源于stack exchange,提问作者ItamarCPS
相关产品推荐
相关产品推荐

