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

线程安全队列:用std::optional替代std::shared_ptr是否可行?

线程安全队列:原书实现与std::optional改写分析

原书(《C++ Concurrency in Action》2012版)的线程安全队列实现

原书采用存储std::shared_ptr<T>的方式实现线程安全队列,核心代码如下:

template<typename T>
class ThreadSafeQueue {
private:
    mutable std::mutex mut;
    std::queue<std::shared_ptr<T>> data_queue;
    std::condition_variable cv;

public:
    ThreadSafeQueue() {}

    void wait_and_pop(T& val) {
        std::unique_lock<std::mutex> lk(mut);
        cv.wait(lk, [this] { return !data_queue.empty(); });
        val = std::move(*data_queue.front());
        data_queue.pop();
    }

    bool try_pop(T& val) {
        std::lock_guard<std::mutex> lk(mut);
        if (data_queue.empty()) {
            return false;
        }

        val = std::move(*data_queue.front());
        data_queue.pop();

        return true;
    }

    std::shared_ptr<T> wait_and_pop() {
        std::unique_lock<std::mutex> lk(mut);
        cv.wait(lk, [this] { return !data_queue.empty(); });
        std::shared_ptr<T> res = data_queue.front();
        data_queue.pop();

        return res;
    }

    std::shared_ptr<T> try_pop() {
        std::lock_guard<std::mutex> lk(mut);
        if (data_queue.empty()) {
            return std::shared_ptr<T>();
        }

        std::shared_ptr<T> res = data_queue.front();
        data_queue.pop();

        return res;
    }

    void push(T val) {
        std::shared_ptr<T> data(std::make_shared<T>(std::move(val)));
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(data);
        cv.notify_one();
    }

    bool empty() const {
        std::lock_guard<std::mutex> lk(mut);

        return data_queue.empty();
    }
}; // class ThreadSafeQueue 

基于std::optional的改写实现

你用std::optional<T>替代空shared_ptr的语义,直接存储T的改写版本代码如下:

template<typename T>
class ThreadSafeQueue {
private:
    mutable std::mutex mut;
    std::queue<T> data_queue;
    std::condition_variable cv;

public:
    ThreadSafeQueue() {}

    void wait_and_pop(T& val) {
        std::unique_lock<std::mutex> lk(mut);
        cv.wait(lk, [this] { return !data_queue.empty(); });
        val = std::move(data_queue.front());
        data_queue.pop();
    }

    bool try_pop(T& val) {
        std::lock_guard<std::mutex> lk(mut);
        if (data_queue.empty()) {
            return false;
        }

        val = std::move(data_queue.front());
        data_queue.pop();

        return true;
    }

    T wait_and_pop() {
        std::unique_lock<std::mutex> lk(mut);
        cv.wait(lk, [this] { return !data_queue.empty(); });
        T res = std::move(data_queue.front());
        data_queue.pop();

        return res;
    }

    std::optional<T> try_pop() {
        std::lock_guard<std::mutex> lk(mut);
        if (data_queue.empty()) {
            return std::optional<T>();
        }

        std::optional<T> res = std::move(data_queue.front());
        data_queue.pop();

        return res;
    }

    void push(T val) {
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(std::move(val));
        cv.notify_one();
    }

    bool empty() const {
        std::lock_guard<std::mutex> lk(mut);

        return data_queue.empty();
    }
}; // class ThreadSafeQueue 

实现合理性与潜在问题分析

合理性部分

你的改写有不少符合现代C++实践的优点:

  • 语义更清晰:用std::optional<T>表示try_pop的空结果,比返回空shared_ptr更直观,符合现代C++错误处理的风格。
  • 性能提升符合预期:直接存储T避免了shared_ptr的引用计数开销,锁内操作的内存成本更低,基准测试的1.3倍性能提升是合理的。
  • 接口优化:wait_and_pop直接返回值的方式,避免了引用传递可能带来的线程安全隐患,符合线程安全代码的最佳实践。

潜在问题与遗漏点

1. 核心异常安全缺陷

原书使用shared_ptr的关键优势是锁内操作无异常:

  • push时在锁外完成std::make_shared(内存分配+对象构造),锁内仅执行shared_ptr的拷贝(noexcept操作),完全避免了锁内异常导致的队列状态不一致。
  • 返回shared_ptr的wait_and_pop/try_pop操作,仅涉及shared_ptr的拷贝(noexcept),不会因对象构造/移动抛出异常。

而你的改写版本存在两处严重的异常安全问题:

  • wait_and_pop()返回T的版本:若T res = std::move(data_queue.front());的移动构造抛出异常,代码仍会执行data_queue.pop();,导致队列元素被移除但值未成功取出,直接造成元素丢失。
  • try_pop()返回std::optional<T>的版本:同理,若std::optional<T> res = std::move(data_queue.front());中T的移动构造抛异常,元素会被pop但res未有效构造,同样导致元素丢失。

2. 锁持有时间变长

原书版本的push操作在锁外完成对象构造,锁内仅执行极快的shared_ptr入队操作;而你的push操作中,锁内的data_queue.push(std::move(val))若涉及T的移动构造(若T的移动构造非noexcept且耗时),会导致锁持有时间变长,增加线程竞争概率,高并发场景下可能抵消部分性能优势。

3. wait_and_pop(T& val)的固有隐患(非改写新增)

这个接口无论原书还是你的实现都存在问题:若val = std::move(data_queue.front());的移动赋值抛出异常,队列中的元素状态会变为有效但未指定,且仍保留在队列中,后续线程取出该元素时可能触发未定义行为。

优化建议

  1. 修复异常安全问题:
    • 若要求T的移动构造为noexcept,可保留当前实现;若需支持移动构造可能抛异常的类型,可参考原书思路,在锁外完成T的构造/移动,锁内仅存储已构造好的对象(比如结合std::optional与shared_ptr的优势)。
  2. 缩短锁持有时间:
    修改push的参数为右值引用并提供重载,让对象的移动尽可能在锁外完成:
    void push(T&& val) {
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(std::move(val));
        cv.notify_one();
    }
    
    void push(const T& val) {
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(val);
        cv.notify_one();
    }
    

内容的提问来源于stack exchange,提问作者Niccolò Tiezzi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:20:01