C++如何实现一个简单的线程安全无锁单向链表(原子指针版)

冬强酱_3935

冬强酱_3935

2026-07-10

968人浏览

原创

不能直接用 std::atomic 操作链表节点指针,因其仅保证指针读写原子性,不保证“读→解引用→改next→写回”整套操作原子性,易引发ABA问题和use-after-free;需结合版本号与延迟回收机制。

c++如何实现一个简单的线程安全无锁单向链表(原子指针版)

为什么不能直接用 std::atomic<t></t> 操作链表节点指针

因为 std::atomic<t></t> 只保证指针本身的读写原子性,不保证「读指针 → 解引用 → 修改 next 字段 → 写回」这一整套操作的原子性。典型问题出现在 pop():你读到 head,准备把它摘下来,但另一线程可能刚把新节点插入到它前面,此时你 CAS 更新 head 会失败——这本身没问题;但更危险的是,若你已解引用了旧 head 并准备 delete 它,而它其实在被其他线程当作中间节点使用(比如正在被 push() 链入),就会造成 use-after-free。

根本矛盾在于:无锁结构必须避免 ABA 问题,且内存回收不能依赖引用计数或垃圾收集器。C++11 起,std::atomic<t></t> 不提供内置的 ABA 防御机制,必须手动扩展指针位宽(如用 std::atomic<uint64_t></uint64_t> 打包指针+版本号)或借助 std::atomic<:shared_ptr></:shared_ptr>(但后者有性能开销且非真正无锁)。

std::atomic<uint64_t></uint64_t> 实现带版本号的原子指针

将指针低 48 位存地址、高 16 位存版本号(假设指针是 48 位有效地址,常见于 x86_64 Linux 的用户空间),每次 CAS 前递增版本号,即可规避 ABA。注意:不能直接对裸指针做位运算,需先转为 uintptr_t

  • 定义节点结构时,next 字段类型必须是 std::atomic<uint64_t></uint64_t>,而非 Node*
  • 读取 next 指针:用 next.load(std::memory_order_acquire),再用位掩码提取地址(如 ptr & 0x0000FFFFFFFFFFFFULL
  • CAS 更新 next:构造新值 (new_ptr & mask) | ((old_version + 1) ,再调用 <code>next.compare_exchange_weak()
  • 务必使用 std::memory_order_acq_rel 或更强序,尤其在 push() 的 CAS 处,否则其他线程可能看到部分初始化的节点

示例片段:

struct Node {
    int data;
    std::atomic<uint64_t> next{0};
<pre class="brush:php;toolbar:false;">Node(int d) : data(d) {}

};

C++ 算法竞赛自动化测试数据生成与校验框架
C++ 算法竞赛自动化测试数据生成与校验框架

根据原题生成新题面、验证器及完整测试数据,自动套用 testlib 模板,用于用户要求生成测试数据时。

下载

struct LockFreeStack { std::atomic head{0};

void push(Node* node) {
    uint64_t old = head.load(std::memory_order_acquire);
    do {
        node->next.store(old, std::memory_order_relaxed);
        // 提取当前 head 地址和版本
        uintptr_t ptr = old & 0x0000FFFFFFFFFFFFULL;
        uint16_t ver = (old >> 48) & 0xFFFF;
        uint64_t desired = (reinterpret_cast<uintptr_t>(node) & 0x0000FFFFFFFFFFFFULL)
                         | (static_cast<uint64_t>(ver + 1) <p>};</p></uint64_t></uintptr_t>

pop() 必须分离「摘链」和「释放内存」两个阶段

这是最容易出错的地方。无锁结构中,一个节点可能被多个线程同时观察到(比如正被遍历),因此不能在 pop() 中直接 delete。必须引入延迟回收机制,例如:Hazard PointerRCU。对于简单场景,可采用「惰性删除 + 周期性扫描」:每个线程维护本地待回收列表,pop() 仅把节点加入该列表;另起后台线程定期检查是否所有线程都已不再访问这些节点,再统一 delete

  • 切勿在 pop() 的 CAS 成功后立刻 delete node —— 此时别的线程可能刚读到该节点地址,正准备访问其 next
  • 如果不用 Hazard Pointer,至少应让 pop() 返回 std::unique_ptr<node></node>,把内存管理责任交给调用方,明确语义边界
  • 即使使用 std::shared_ptr 管理节点,也要注意:shared_ptr 的控制块本身不是无锁的,use_count 的修改仍涉及锁,不符合“无锁”定义

单向链表的无锁 find() 和遍历为何天然安全

因为只读操作不修改结构,只要节点一旦被 push() 成功链入,其 next 字段就不会再被原线程改写(后续修改只发生在新插入节点上)。所以从 head 开始逐个读 next 是安全的,无需额外同步。

  • 遍历时用 std::memory_order_acquire 读每个节点的 next,确保看到该节点完整初始化的数据(如 data 字段)
  • 但要注意:遍历结果不具快照一致性 —— 中间可能有节点被其他线程 pop() 掉,你仍会读到它的 data,只是下一步 next 会是 null 或跳到别处
  • 如果业务需要强一致性遍历(比如统计长度),就必须加锁,或者改用带标记删除的双链表 + 垃圾收集方案

真正的难点不在链表逻辑本身,而在内存生命周期管理。很多人卡在 CAS 成功后删节点,却忘了其他线程栈上可能还存着那个地址的拷贝。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
c++和c语言的区别有哪些
c++和c语言的区别有哪些

c++和c语言的区别:1、面向对象编程(OOP)支持不同;2、新增特性不同;3、标准库不同;4、编译方式不同;5、命名空间不同等等。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.14

2068

9

c++和python学习顺序推荐
c++和python学习顺序推荐

一般建议先学习C++,再学习Python,因为这样可以逐步从较为底层的编程语言向更高级的语言过渡。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

959

6

python和c++学习性价比分析
python和c++学习性价比分析

Python易于学习,广泛应用于Web开发、数据科学和人工智能等领域,但性能较低。C语言性能高,适用于对性能要求较高的场景,如游戏开发和系统编程,但学习曲线陡峭,错误处理复杂。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

367

5

c语言和c++一样吗
c语言和c++一样吗

c语言和c++是两种不同的编程语言,虽然有相似之处,但存在显著差异。c语言专注于过程式编程和系统级开发,以简洁、高效著称。c++作为c语言的超集,引入了面向对象编程,增强了代码组织和管理能力,但学习曲线也更陡峭。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

307

5

c语言和c++先学哪个好
c语言和c++先学哪个好

初学者选择学习c语言还是c++语言,需要根据个人学习目标、背景以及编程兴趣和预期应用方向来决定。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

346

5

c语言和c++的区别和联系
c语言和c++的区别和联系

c语言和c++是计算机科学领域应用广泛的编程语言。虽然它们有着相似的基础,但它们在语言类型、语法功能和内存管理方面存在着显著差异。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

560

5

c++软件中文更改教程
c++软件中文更改教程

对于 ide,可通过打开设置,找到语言设置,选择中文,并保存更改。对于非 ide 应用程序,可查找设置或选项,选择语言设置,更改为中文,并保存更改。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.21

1389

9

python和java和c++学习性价比分析
python和java和c++学习性价比分析

Python以其易学性、丰富的库和活跃的社区而著称,适合数据科学、人工智能和Web开发。Java以其跨平台性、企业级应用开发和Android应用开发而闻名。C++以其底层控制能力、高效性能和游戏开发而著称。选择哪种语言取决于个人兴趣、职业方向和特定需求。想了解更多python和java和c++的相关内容,可以阅读本专题下面的文章。

2024.03.22

1177

7

c++和c语言学习顺序推荐
c++和c语言学习顺序推荐

对于初学者,建议先学习C语言,掌握编程基础后再转入C++,便于理解面向对象编程概念。有编程经验者可直接学习C++,快速接触高级编程技术。想了解更多c++和c语言的相关内容,可以阅读本专题下面的文章。

2024.03.25

1305

9

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Conan 2 Essentials 免费课程
Conan 2 Essentials 免费课程

共0课时 | 0人学习

CMake 与 Conan 集成实践
CMake 与 Conan 集成实践

共0课时 | 0人学习

Conan 2 高级依赖模型介绍
Conan 2 高级依赖模型介绍

共0课时 | 0人学习