You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 15:10:16