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

原子智能指针(atomic shared_ptr)在无锁栈中的优势解析

无锁栈场景下std::atomic<std::shared_ptr<Node>>对比std::atomic<Node*>的优势

我在查找atomic shared_ptr的示例与应用场景时,发现了一个无锁栈实现,该实现中Node结构体的next成员使用shared_ptr<Node>,栈的head则存储为atomic<shared_ptr<Node>>。代码示例如下:

template <typename T>
struct Stack {
    struct Node {
        T t;
        shared_ptr<Node> next;
    };

    atomic<shared_ptr<Node>> head;

    void push_front(T t) {
        auto p = make_shared<Node>(std::move(t), head.load());
        while (!head.compare_exchange_weak(p->next, p)) {}
    }

    optional<T> pop_front() {
        auto p = head.load();
        while (p != nullptr && !head.compare_exchange_weak(p, p->next)) {}
        if (p != nullptr)
            return {std::move(p->t)};
        else
            return {};
    }
};

我多次检查代码并深究细节,但仍未发现使用atomic shared_ptr替代atomic裸指针的优势。以下是我实现的版本:

template <typename T>
struct Stack {
    struct Node {
        T t;
        Node* next;
    };

    std::atomic<Node*> head;

    void push_front(T t) {
        auto p = new Node(std::move(t), head.load());
        while (!head.compare_exchange_weak(p->next, p)) {}
    }

    std::optional<T> pop_front() {
        auto p = head.load();
        while (p != nullptr && !head.compare_exchange_weak(p, p->next)) {}
        if (p != nullptr)
            return { std::move(p->t) };
        else
            return {};
    }
};

请问在此场景下使用atomic shared_ptr的优势是什么?


核心优势解释

  • 彻底解决内存安全问题
    你的裸指针版本存在两个致命缺陷:一是pop_front取出节点后未释放内存,必然引发内存泄漏;二是多线程环境下,线程A加载head得到指针p后,线程B可能已经将p对应的节点弹出并销毁,此时线程A访问p->next会触发悬空指针,导致未定义行为。
    而atomic<shared_ptr<Node>>依托引用计数自动管理内存:节点的生命周期由所有持有它的shared_ptr共同决定,只有当最后一个shared_ptr被销毁时,节点才会自动释放。完全无需手动处理内存释放,从根源上避免了泄漏和悬空指针问题。

  • 原子操作与引用计数强绑定
    std::atomic<std::shared_ptr>的原子操作(如load、compare_exchange_weak)会同步更新引用计数。比如调用head.load()时,不仅原子性地获取当前head指针,还会原子性地递增该指针的引用计数,确保当前线程持有的shared_ptr在其他线程修改head时仍然有效。
    裸指针的原子操作仅针对指针值本身,没有引用计数保护。一旦节点被其他线程销毁,当前线程持有的裸指针就会变成无效的悬空指针。

  • 避免手动维护引用计数的复杂度
    如果想用裸指针实现安全的无锁栈,必须手动给每个节点添加原子引用计数,在指针传递、加载、释放时手动增减计数,还要处理计数归零后的销毁逻辑。这会大幅增加代码复杂度,且极易因疏忽导致计数错误(比如漏增漏减),引发难以调试的问题。atomic<shared_ptr>已经封装了所有这些逻辑,代码简洁且不易出错。

内容的提问来源于stack exchange,提问作者Cabbas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:47:03