0

0

c++如何实现一个无锁(lock-free)栈? (ABA问题详解)

裘德小鎮的故事

裘德小鎮的故事

发布时间:2026-01-12 11:25:02

|

494人浏览过

|

来源于php中文网

原创

直接用 std::atomic 实现无锁栈会因 ABA 问题导致链表破坏、访问释放内存或崩溃;需用指针+版本号打包、hazard pointer 或 RCU 等方案解决,并严格配对 memory_order_acquire/release。

c++如何实现一个无锁(lock-free)栈? (aba问题详解)

为什么直接用 std::atomic 实现会出问题

无锁栈的核心是用 compare_exchange_weak 原子地更新栈顶指针,但仅靠它无法防止 ABA 问题:某个节点 A 被弹出(变为闲置),又被新节点复用(地址相同),此时另一个线程还在尝试用旧的「A→B」快照做 CAS,会误认为状态未变而成功——结果链表被破坏。

典型现象是 pop() 返回错误节点、top() 访问已释放内存、程序崩溃或静默数据错乱。

关键不是“能不能编译”,而是“多线程高并发下行为是否可预测”。即使测试跑一万次不崩,也可能在生产环境每小时触发一次 ABA。

std::atomic + ABA 计数器绕过地址复用歧义

把指针和一个单调递增的版本号打包进一个足够宽的整数(如 64 位),高位存指针,低位存计数。每次修改栈顶时计数器自增,确保即使地址重复,组合值也不同。

立即学习C++免费学习笔记(深入)”;

常见做法是用 uintptr_t 的低 16 位作计数器(支持 65536 次重用),剩余高位存指针——前提是系统指针地址天然对齐(如 x86_64 下指针最低 3 位恒为 0,实际可用更多位)。

皮卡智能
皮卡智能

AI驱动高效视觉设计平台

下载
  • push() 时:读当前 head → 构造新节点 → 用 atomic_load 获取当前组合值 → 提取旧指针和计数 → 新组合 = (new_node_ptr
  • pop() 时:同样拆解组合值,CAS 比较整个 uintptr_t,失败则重试
  • 注意:必须保证节点分配器(如 new)不立即复用刚 delete 的内存;否则计数器没来得及增长,ABA 就重现

更稳妥的做法:用 hazard pointerRCU 配合引用计数

ABA 的本质是内存回收时机失控。与其在指针上硬加版本号,不如显式管理节点生命周期:

  • 每个线程维护自己的 hazard pointer,指向当前正在访问的节点(如 pop 中读到的 next
  • pop() 流程变成:读 head → 写入 hazard pointer → 再次确认 head 未变 → CAS 更新 → 若成功,将旧头节点加入待回收队列
  • 回收器定期扫描所有线程的 hazard pointer,只释放那些「不在任何 hazard pointer 中,且无其他引用」的节点
  • 这比纯计数器方案稍重,但彻底消除 ABA,且兼容任意内存分配策略

标准库不提供 hazard pointer,需手写或用 libcds 等第三方库。C++20 的 std::atomic 也不能直接用于无锁栈——因为 shared_ptr 的控制块修改本身不是无锁的。

别忘了内存序:用 memory_order_acquirememory_order_release 控制可见性

即使解决了 ABA,错误的内存序仍会导致乱序读写。例如:

Node* old_head = head.load(std::memory_order_acquire);
// 如果这里不加 acquire,编译器/CPU 可能把后续对 old_head->next 的读取提前到 load 之前
Node* new_head = old_head->next;
// CAS 必须用 release,确保 new_head 的写入对其他线程可见
head.compare_exchange_weak(old_head, new_head, std::memory_order_acq_rel);

常见错误是全用 memory_order_relaxed——它只保证原子性,不约束前后普通内存访问顺序,极易引发竞态。

真正难的从来不是写个能跑的无锁栈,而是让每个 loadstoreCAS 的内存序都精准匹配硬件模型和算法逻辑。漏掉一个 acquire,就可能在某台 NUMA 机器上稳定复现崩溃。

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

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

下载

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

相关专题

更多
堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

386

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

569

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

480

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

143

2025.12.24

数据库Delete用法
数据库Delete用法

数据库Delete用法:1、删除单条记录;2、删除多条记录;3、删除所有记录;4、删除特定条件的记录。更多关于数据库Delete的内容,大家可以访问下面的文章。

269

2023.11.13

drop和delete的区别
drop和delete的区别

drop和delete的区别:1、功能与用途;2、操作对象;3、可逆性;4、空间释放;5、执行速度与效率;6、与其他命令的交互;7、影响的持久性;8、语法和执行;9、触发器与约束;10、事务处理。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.12.29

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

399

2023.08.14

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

78

2026.01.09

c++框架学习教程汇总
c++框架学习教程汇总

本专题整合了c++框架学习教程汇总,阅读专题下面的文章了解更多详细内容。

46

2026.01.09

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.6万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 18.7万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号