【C/C++】无锁队列实现与内存回收机制:Hazard Pointer 深度解析

无锁队列实现与内存回收机制:Hazard Pointer 深度解析

在并发系统中,为了提升性能和避免锁竞争,我们常常追求 lock-free 数据结构。但当你实现完一个无锁队列后,会发现一个严重问题:

内存什么时候释放?怎样避免访问已经被回收的节点?

Hazard Pointer(危险指针)机制,就是为了解决这一痛点。

本篇将带你深入理解:

  • 什么是无锁队列(MS Queue)
  • 为什么需要 Hazard Pointer
  • 如何用 C++ 实现完整的无锁队列 + 安全回收机制
  • 避免 ABA 问题的技巧

一、无锁队列背景:MS 队列简介

Michael-Scott 队列(MS Queue)是经典的无锁并发队列,具备以下特性:

  • 使用 std::atomic 实现
  • 支持多生产者、多消费者
  • 基于 CAS(Compare-And-Swap)进行并发控制

队列结构:

   head --> [dummy] --> [node1] --> [node2] --> nullptr^tail
  • 使用 dummy 节点初始化队列,避免空指针特判。
  • enqueue 修改 tail,dequeue 修改 head。

二、C++ 无锁队列实现(简化版)

template <typename T>
struct Node {std::atomic<Node*> next;T value;Node(const T& val) : next(nullptr), value(val) {}
};template <typename T>
class LockFreeQueue {
private:std::atomic<Node<T>*> head;std::atomic<Node<T>*> tail;public:LockFreeQueue() {Node<T>* dummy = new Node<T>(T{});head.store(dummy);tail.store(dummy);}void enqueue(const T& val) {Node<T>* new_node = new Node<T>(val);while (true) {Node<T>* last = tail.load();Node<T>* next = last->next.load();if (last == tail.load()) {if (!next) {if (last->next.compare_exchange_weak(next, new_node)) {tail.compare_exchange_weak(last, new_node);return;}} else {tail.compare_exchange_weak(last, next);}}}}bool dequeue(T& result) {while (true) {Node<T>* first = head.load();Node<T>* last = tail.load();Node<T>* next = first->next.load();if (first == head.load()) {if (first == last) {if (!next) return false; // emptytail.compare_exchange_weak(last, next);} else {result = next->value;if (head.compare_exchange_weak(first, next)) {// ❗问题:何时 delete first ?return true;}}}}}
};

问题:节点释放时机?

在并发环境下 dequeue 之后,其他线程可能仍在访问那个被删除的节点,不能立即 delete。


三、为什么需要 Hazard Pointer?

常见方法(失败):

  • 延迟 delete? 不知道什么时候没人访问。
  • GC? C++ 没有自动垃圾回收。
  • 引用计数? 有性能开销,难以 lock-free。
  • 线程局部链表回收? 难以同步他人状态。

Hazard Pointer 的核心思想:

每个线程维护一个“我正在访问的指针”集合,回收前先检查是否有人正在访问。


四、Hazard Pointer 机制原理

数据结构:

// 线程局部
thread_local Node* my_hazard_ptr;// 全局列表
std::vector<std::atomic<Node*>> hazard_pointers;

工作流程:

  1. 访问某节点前,将其地址写入 hazard pointer。

  2. 删除时,把要删除的节点放入“回收候选集合”。

  3. 定期扫描所有线程的 hazard pointer:

    • 如果节点不在任何 hazard 中,则可以安全 delete。

回收伪码:

void retire_node(Node* node) {retired_list.push_back(node);if (retired_list.size() >= threshold) {scan_and_reclaim();}
}void scan_and_reclaim() {std::unordered_set<Node*> hp_set;for (auto& hp : hazard_pointers) {Node* p = hp.load();if (p) hp_set.insert(p);}auto it = retired_list.begin();while (it != retired_list.end()) {if (hp_set.count(*it) == 0) {delete *it;it = retired_list.erase(it);} else {++it;}}
}

五、完整工程结构(建议)

lockfree_queue/
├── include/
│   ├── lockfree_queue.h         // MS队列
│   └── hazard_pointer.h         // Hazard机制封装
├── src/
│   ├── hazard_pointer.cpp
│   └── main.cpp
├── test/
│   └── test_queue.cpp           // GTest 测试用例
├── CMakeLists.txt

示例:Hazard 指针封装类接口

class HazardPointerManager {
public:static void* get_slot(); // 获取当前线程 hazard pointer 槽static void retire(void* ptr);static void reclaim(); // 回收所有未被引用的节点
};

六、ABA 问题与 Hazard Pointer 的结合防护

虽然 Hazard Pointer 可以避免野指针访问,但不能防止 ABA 问题。

因此,通常会在无锁队列中配合使用 Tagged Pointer(带版本号的指针)

struct TaggedPtr {Node* ptr;uint64_t tag;
};
std::atomic<TaggedPtr> head;

通过不断增加 tag 可以有效防止 ABA。


七、总结与建议

问题是否解决说明
并发入队CAS 更新尾指针
并发出队CAS 更新头指针
节点回收Hazard Pointer 扫描后安全 delete
ABA 问题✅(需 TaggedPtr)防止假象“未变化”

八、参考资料

  • Michael & Scott: “Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms”
  • Herb Sutter: “Lock-Free Programming (Hazard Pointers)”
  • C++ Concurrency in Action 第二版
  • Facebook Folly Hazard Pointer 实现

下期预告

《设计一个可扩展的 Lock-Free 环:支持动态扩容、线程绑定与优先级任务》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/88605.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/88605.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Scrapy进阶封装(第三阶段:多管道封装,多文件存储)

1.yield返回数据的原理? 为什么要用yield返回数据给管道&#xff1f; 遍历这个函数的返回值的时候&#xff0c;挨个把数据读到内存&#xff0c;不会造成内存的瞬间占用过高&#xff0c;Python3中的range和python2中的xrange同理。scrapy是异步爬取&#xff0c;所以通过yield…

证照大师 MAX 4.0安装与基础功能体验(附流程演示)

软件介绍 证照大师 MAX 4.0是一款功能强大的证件照制作软件&#xff0c;专为满足用户不同场景下的证件照需求而设计。它整合了专业的照片处理技术和智能化的操作系统&#xff0c;提供了自动抠图、尺寸调整、美颜处理、批量处理以及格式转换等多种功能。该软件用户界面简洁明快…

RK3568-适配mipi屏幕触摸和显示

1.1 适配mipi屏幕触摸 gt9xx_lvds: gt9xx-lvds5d {compatible "goodix,gt9xx";reg <0x5d>;pinctrl-names "default";pinctrl-0 <&touch_gpio>;touch-gpio <&gpio1 RK_PA4 IRQ_TYPE_LEVEL_LOW>;reset-gpio <&gpio1…

ICME 2025音频编码器能力挑战赛Workshop即将举办!

IEEE International Conference on Multimedia and Expo 2025&#xff08;ICME 2025&#xff09; 将于 6月30日至7月4日在法国南特举行。作为全球多媒体领域的顶级会议之一&#xff0c;ICME 2025 汇聚全球顶尖学者与产业专家&#xff0c;聚焦人工智能驱动的多媒体技术&#xff…

物奇微WQ5007A上手指南

一、获取SDK 需要与物奇微电子股份有限公司签订NDA协议才会提供SDK。 二、搭建开发环境 SDK里包含了编译工具、开发文档、源码。在windows系统下搭建开发环境&#xff1a; 1、安装交叉编译工具 将\wuqi_sdk\tools\riscv64-unknown-elf-gcc-10.2.0-windows.zip文件解压到任…

[论文阅读] 人工智能 + 软件工程 | LLM在单元测试中的应用:系统性综述与未来展望

LLM在单元测试中的应用&#xff1a;系统性综述与未来展望 论文信息 arXiv:2506.15227 Large Language Models for Unit Testing: A Systematic Literature Review Quanjun Zhang, Chunrong Fang, Siqi Gu, Ye Shang, Zhenyu Chen, Liang Xiao Subjects: Software Engineering …

数据重叠对CLIP零样本能力影响CLIP论文图17笔记

这两张图表&#xff08;图17左、右图&#xff09;是CLIP论文中验证“数据重叠是否影响CLIP零样本能力”的关键证据&#xff0c;核心是通过**“数据重叠分析”排除CLIP“作弊”嫌疑**&#xff08;即CLIP的高零样本准确率是否因为“见过测试集图像”&#xff09;。下面用“先看懂…

996引擎-假人系统

996引擎-假人系统 lua 假人问题添加假人名字列表打开M2设置假人参考资料 lua 假人问题 添加假人名字列表 假人名字列表 Mir200\Envir\DummyNameList.txt 打开M2设置假人 【选项】>【假人设置】 参考资料 假人系统

Rk3568驱动开发_Key驱动_13

设备树配置 key{compatible "alientek,key";pinctrl-0 <&key_gpio>;pinctrl-names "alientek,key";key-gpio <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;status "okay";};配置信息方便后面直接引用&#xff1a; // Narnat 2025…

参展回顾 | AI应用创新场景:数据分析助手ChatBI、璞公英教学平台亮相2025四川国际职教大会暨产教融合博览会

2025年6月11日-13日&#xff0c;以“数字赋能产教融合&#xff0c;创新驱动技能未来”为主题的2025四川国际职业教育大会暨产教融合博览会在成都盛大开幕。璞华联合百度共同参展&#xff0c;并携旗下创新产品ChatBI数据分析助手、璞公英教学平台重磅亮相&#xff0c;凭借前沿的…

动态规划之01背包问题

动态规划算法 动态规划算法介绍 动态规划(Dynamic Programming)算法的核心思想是&#xff1a;将大问题划分为小问题进行解决&#xff0c;从而一步步获取最优解的处理算法动态规划算法与分治法类似&#xff0c;其基本思想也是将待解决问题分解成若干个子问题&#xff0c;先求解…

人大金仓新建用户,并且赋值查询权限

-- 1. 创建用户 visitor&#xff0c;并且设置密码 CREATE USER visitor WITH PASSWORD 1234qwer; -- 2. 授予该用户连接到数据库 "yonbip_db" 的权限 GRANT CONNECT ON DATABASE yonbip_db TO visitor; -- 3. 假设你要让 visitor 查询的模式是 public&#xff08;或…

学习笔记丨信号处理新趋势:量子计算将如何颠覆传统DSP?

在算力需求爆炸式增长的今天&#xff0c;传统数字信号处理&#xff08;DSP&#xff09;芯片正面临物理极限的严峻挑战。当经典计算机架构在摩尔定律的黄昏中挣扎时&#xff0c;量子计算正以颠覆性姿态崛起&#xff0c;准备重新定义信号处理的未来图景。 目录 传统DSP的瓶颈&am…

react day.js使用及经典场景

简介 Day.js 是一个轻量级的 JavaScript 日期库&#xff0c;它提供了简单易用的 API 来处理日期和时间。以及更加轻量级&#xff0c;并且具有更快的性能。 安装 npm install dayjs 使用 import dayjs from "dayjs";dayjs().format("YYYY-MM-DD HH:mm:ss&qu…

【机器学习深度学习】线性回归

目录 一、定义 二、举例说明 三、 数学形式 四、 训练过程&#xff08;机器怎么学会这条线&#xff1f;&#xff09; 五、在 PyTorch 中怎么实现线性回归&#xff1f; 六、如果你学懂了线性回归&#xff0c;你也能理解这些 七、综合应用&#xff1a;线性回归示例 7.1 执…

如何在 Manjaro Linux 上安装 .NET Core

.NET 是一个开源的开发框架平台,可在所有流行的操作系统(如 Windows、Linux 和 macOS)上免费使用和安装。它是跨平台的,是主要由微软员工在 .NET 基金会下开发的专有 .NET Framework 的继承者。.NET 是一个统一的平台,用于开发各种操作系统上的软件,如 Web、移动、桌面应…

Mysql解惑(一)

使用 or 可能不走索引 使用 union替代 使用in&#xff0c;可能不走索引 如果优化&#xff1a; 临时表强制索引exists代替

基于机器学习的侧信道分析(MLSCA)Python实现(带测试)

一、MLSCA原理介绍 基于机器学习的侧信道分析(MLSCA)是一种结合传统侧信道分析技术与现代机器学习算法的密码分析方法。该方法通过分析密码设备运行时的物理泄漏信息(如功耗、电磁辐射等)&#xff0c;利用机器学习模型建立泄漏数据与密钥信息之间的关联模型&#xff0c;从而实…

【LLM】位置编码

【LLM】位置编码 1 绝对位置嵌入为什么用 1000 0 2 t d 10000^{\frac{2t}{d}} 10000d2t​? 2 相对位置嵌入2.1 Shaw等人的方法&#xff08;2018&#xff09;2.2 Dai等人的方法&#xff08;2019&#xff09;2.3 Raffel 等人的方法&#xff08;2020&#xff09;2.4 He 等人的方法…

Java 根据分组key构建合并数据集

文章目录 前言背景总结 前言 请各大网友尊重本人原创知识分享&#xff0c;谨记本人博客&#xff1a;南国以南i、 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 背景 Java 需要返回一组数据供前端展示&#xff0c;获取到的数据格式如下&#xff1a; …