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

《C++并发编程实战》中为何用引用计数/hazard变量而非std::atomic<std::shared_ptr>?

关于无锁栈中原子智能指针的疑问

我正在学习Anthony Williams所著《C++并发编程实战》中的无锁数据结构,书中示例为无锁栈。Williams在第186页给出了pop操作的伪代码:

  • 读取head的当前值;
  • 读取head->next;
  • 将head设置为head->next;
  • 返回取出节点的数据;
  • 删除取出的节点。

不含步骤5的代码如下:

std::shared_ptr<T> pop() {
    node* old_head=head.load();
    while(old_head &&
        !head.compare_exchange_weak(old_head,old_head->next));
    return old_head ? old_head->data : std::shared_ptr<T>();
}

书中指出:若一个线程执行完步骤5时,另一个线程刚执行到步骤2,后者会解引用悬空指针。随后提出了引用计数/垃圾回收等解决方案。我的问题是:为何不能直接用原子智能指针封装节点?它们难道不能自动完成资源清理吗?


核心原因:原子操作的"可见性"与智能指针的局限性冲突

直接用std::atomic<std::shared_ptr<node>>封装节点看似能解决问题,但实际存在几个关键障碍:

  1. 普通std::shared_ptr的引用计数非原子(C++20前)
    早期C标准中,std::shared_ptr的引用计数修改默认不具备线程安全性。即便C20引入了std::atomic_shared_ptr,它也只保证指针本身的原子读写,节点内部的next指针依然是普通指针——当线程A原子替换head并让节点引用计数归零销毁时,线程B可能正卡在读取old_head->next的步骤,依然会触发悬空指针解引用。

  2. 无锁场景下的"延迟销毁"需求无法被智能指针满足
    智能指针的自动回收依赖明确的引用关系,但无锁环境中,线程对节点的访问是无同步的瞬时操作:你弹出节点后,引用计数可能立刻降到0,但此时仍有其他线程持有该节点的指针(比如正执行步骤2的线程)。智能指针无法感知这种潜在的访问,直接销毁节点必然导致崩溃。

  3. 原子智能指针的操作粒度无法覆盖完整访问周期
    就算用std::atomic_shared_ptr,读取head和读取head->next是两个独立的原子操作,中间存在时间窗口:线程A读取old_head后,线程B弹出并销毁该节点,线程A再去访问old_head->next时依然会踩悬空指针。原子智能指针只能保证指针本身的原子性,无法保证指针指向的对象在整个访问周期内存活。

总结

智能指针的自动清理需要明确的引用边界,但无锁数据结构中线程间的访问无同步机制,无法确保节点在所有潜在访问结束后再销毁。因此必须借助额外的节点级引用计数、 Hazard Pointer(危险指针)或垃圾回收机制,来实现节点的安全延迟销毁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 05:07:26