当前位置: 首页 > news >正文

深入解析C++原子指针std::atomic

C++ 标准库中的 std::atomic 针对指针类型的特化版本:struct atomic<_Tp*> : public __atomic_base<_Tp*>

这是一个非常重要的模板特化,它为 C++ 中的指针提供了线程安全、无锁的原子操作能力。

atomic<_Tp*> 是什么?提供了什么?

std::atomic<T*> 是 std::atomic 模板类的一个特化版本。当模板参数 T 是一个指针类型时(例如 int*Node*),编译器就会选择这个特化版本,而不是通用的 atomic<_Tp> 主模板。

它的核心目标是:将一个指针变量的访问和修改变成原子操作,从而在多线程环境下安全地共享和操作这个指针,而无需使用互斥锁(mutex)。

除了通用的原子操作(如 loadstorecompare_exchange 等,这些由基类 __atomic_base 提供)之外,这个指针特化版本还提供了原子化的指针算术运算。这是它与通用版本最核心的区别。

下面我们逐一分析它提供的接口:

1. 结构与类型定义
// ... existing code ...
template <class _Tp>
struct atomic<_Tp*> : public __atomic_base<_Tp*> {using __base          = __atomic_base<_Tp*>;using value_type      = _Tp*;using difference_type = ptrdiff_t;
// ... existing code ...
  • 继承关系: 它公有继承自 __atomic_base<_Tp*>。这是一种常见的实现技巧,__atomic_base 封装了所有 atomic 类型都通用的操作,如 loadstoreexchangecompare_exchange_weak/strong 等,避免了代码重复。
  • value_type: 定义为 _Tp*,表明这个原子类型所包装的值是一个指针。
  • difference_type: 定义为 ptrdiff_t。这是一个标准类型,用于表示两个指针之间的距离(以字节为单位)。这为后续的指针算术运算奠定了基础。
2. 构造函数与赋值操作
// ... existing code ..._LIBCPP_HIDE_FROM_ABI atomic() _NOEXCEPT = default;_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR atomic(_Tp* __d) _NOEXCEPT : __base(__d) {}_LIBCPP_HIDE_FROM_ABI _Tp* operator=(_Tp* __d) volatile _NOEXCEPT {__base::store(__d);return __d;}_LIBCPP_HIDE_FROM_ABI _Tp* operator=(_Tp* __d) _NOEXCEPT {__base::store(__d);return __d;}atomic& operator=(const atomic&)          = delete;atomic& operator=(const atomic&) volatile = delete;
// ... existing code ...
  • 构造函数: 提供了一个默认构造函数(通常将指针初始化为 nullptr)和一个接受普通指针 _Tp* 的构造函数。
  • 赋值运算符 (operator=): 重载了赋值运算符,使其行为等同于一次原子的 store 操作。这意味着 my_atomic_ptr = new_ptr; 是一次线程安全的操作。
  • 拷贝构造/赋值被删除 (= delete): 这是 atomic 类型的核心特征。atomic 对象本身是不可拷贝、不可移动的。因为拷贝一个 atomic 变量本身不是一个原子操作,这会破坏其线程安全性。你只能拷贝它所管理的值。
3. 核心功能:原子指针算术

这是 atomic<_Tp*> 特化版本最关键的部分。

// ... existing code ..._LIBCPP_HIDE_FROM_ABI _Tp* fetch_add(ptrdiff_t __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT {// ...return std::__cxx_atomic_fetch_add(std::addressof(this->__a_), __op, __m);}_LIBCPP_HIDE_FROM_ABI _Tp* fetch_sub(ptrdiff_t __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT {// ...return std::__cxx_atomic_fetch_sub(std::addressof(this->__a_), __op, __m);}
// ... existing code ...
  • fetch_add / fetch_sub:
    • 功能: 原子地将指针的值增加或减少指定的字节数 __op。这是一个经典的 "Read-Modify-Write" (RMW) 操作。
    • 返回值: 返回指针在进行加/减操作之前的旧值。
    • 安全性static_assert(!is_function<...>::value) 确保了你不能对函数指针进行算术运算,因为这是 C++ 标准所禁止的。
4. 语法糖:重载算术运算符

为了使用起来更方便,atomic<_Tp*> 重载了我们熟悉的递增/递减和复合赋值运算符。

// ... existing code ..._LIBCPP_HIDE_FROM_ABI _Tp* operator++(int) volatile _NOEXCEPT { return fetch_add(1); }_LIBCPP_HIDE_FROM_ABI _Tp* operator++(int) _NOEXCEPT { return fetch_add(1); }_LIBCPP_HIDE_FROM_ABI _Tp* operator--(int) volatile _NOEXCEPT { return fetch_sub(1); }_LIBCPP_HIDE_FROM_ABI _Tp* operator--(int) _NOEXCEPT { return fetch_sub(1); }_LIBCPP_HIDE_FROM_ABI _Tp* operator++() volatile _NOEXCEPT { return fetch_add(1) + 1; }_LIBCPP_HIDE_FROM_ABI _Tp* operator++() _NOEXCEPT { return fetch_add(1) + 1; }_LIBCPP_HIDE_FROM_ABI _Tp* operator--() volatile _NOEXCEPT { return fetch_sub(1) - 1; }_LIBCPP_HIDE_FROM_ABI _Tp* operator--() _NOEXCEPT { return fetch_sub(1) - 1; }_LIBCPP_HIDE_FROM_ABI _Tp* operator+=(ptrdiff_t __op) volatile _NOEXCEPT { return fetch_add(__op) + __op; }_LIBCPP_HIDE_FROM_ABI _Tp* operator+=(ptrdiff_t __op) _NOEXCEPT { return fetch_add(__op) + __op; }_LIBCPP_HIDE_FROM_ABI _Tp* operator-=(ptrdiff_t __op) volatile _NOEXCEPT { return fetch_sub(__op) - __op; }_LIBCPP_HIDE_FROM_ABI _Tp* operator-=(ptrdiff_t __op) _NOEXCEPT { return fetch_sub(__op) - __op; }
// ... existing code ...
  • 后缀递增/递减 (p++p--): 实现为直接调用 fetch_add(1) 或 fetch_sub(1)。这完全符合后缀运算符的语义:返回旧值,然后执行增/减。
  • 前缀递增/递减 (++p--p): 实现为 fetch_add(1) + 1。这里需要仔细理解:
    1. fetch_add(1) 原子地将指针加 1,并返回加 1 之前的旧值。
    2. 然后,代码将这个旧值再加 1,得到的就是加 1 之后的新值。
    3. 这个新值被返回,完全符合前缀运算符的语义。整个 RMW 操作的原子性由 fetch_add 保证。
  • 复合赋值 (+=-=): 逻辑与前缀运算符类似,返回的是运算后的新值。

底层如何实现?

std::atomic 的魔法来源于它并非普通的 C++ 代码,而是深度依赖于编译器和硬件

1. 编译器内置函数 (Compiler Intrinsics)

观察实现代码: return std::__cxx_atomic_fetch_add(std::addressof(this->__a_), __op, __m);

这里的 std::__cxx_atomic_fetch_add 是一个关键。它通常不是用 C++ 实现的,而是 C++ 标准库对编译器内置函数(Compiler Intrinsics)的一层封装。对于 GCC 和 Clang,这个内置函数就是 __atomic_fetch_add

当编译器看到 __atomic_fetch_add 这样的内置函数时,它不会去寻找这个函数的 C++ 定义,而是会直接将其翻译成目标 CPU 架构支持的原子指令

2. 硬件原子指令

CPU 硬件层面提供了一些特殊的指令,可以保证一个“读-改-写”的操作序列在执行时不会被其他 CPU核心中断。这正是实现原子操作的基础。

  • x86/x86-64 架构fetch_add 很可能会被编译成 LOCK XADD 指令。
    • XADD 指令本身就能完成“交换值并相加”的操作。
    • LOCK 是一个指令前缀,它会锁住总线(或更现代的 CPU 中的缓存行),确保在 XADD 指令执行期间,其他核心无法访问这块内存。这就保证了整个操作的原子性。
  • ARM 架构: 可能会被编译成 LDREX (Load-Exclusive) 和 STREX (Store-Exclusive) 指令对。
    • LDREX 读取一个地址的值,并“标记”这个地址。
    • 之后进行修改计算。
    • STREX 尝试将新值写回。如果在此期间没有其他核心修改过这个被标记的地址,写入成功。如果被修改过,写入失败,需要整个操作重试(通常是一个循环)。
3. 内存顺序 (Memory Order)

memory_order __m 参数是原子操作的另一个核心。它告诉编译器和 CPU,这次原子操作需要提供何种级别的内存可见性保证。

  • memory_order_relaxed: 最宽松。只保证操作本身的原子性,不提供任何跨线程的顺序保证。性能最高。
  • memory_order_acquire: 获取语义。保证在此操作之后的所有读写,都不能重排到此操作之前。常用于“解锁”或读取共享数据。
  • memory_order_release: 释放语义。保证在此操作之前的所有读写,都不能重排到此操作之后。常用于“加锁”或写入共享数据。
  • memory_order_acq_rel: 同时具备 acquire 和 release 语义。
  • memory_order_seq_cst: 最严格。保证所有线程看到的所有 seq_cst 操作的顺序是一致的,全局同步。性能开销最大。

通过选择合适的内存顺序,可以在保证程序正确性的前提下,获得极致的性能。

总结

std::atomic<_Tp*> 是 C++ 提供的一个强大工具,它通过模板特化,为指针类型赋予了线程安全的能力。

  • 它提供了:除了通用的原子读、写、比较并交换(CAS)之外,还特别提供了原子化的指针算术运算,并以重载运算符的形式提供了方便的语法糖。
  • 它的实现:依赖于编译器内置函数,这些函数会被直接翻译成目标平台的硬件原子指令(如 x86 的 LOCK XADD),从而在硬件层面保证了操作的不可分割性,实现了高效的无锁并发。
http://www.dtcms.com/a/300021.html

相关文章:

  • GIS地理信息系统建设:高精度3D建模
  • [Linux入门] 初学者入门:Linux DNS 域名解析服务详解
  • Anaconda常用命令及环境管理指南
  • kali [DNS劫持] 实验(详细步骤)
  • Containerd简介
  • 【Ubuntu】发展历程
  • 如何做数据增强?
  • 框架式3D打印机结构设计cad【9张】三维图+设计说明书
  • 【升级U8+】不能将值 NULL 插入列 ‘LocaleID‘,表 ‘hr_sys_function_Base‘;列不允许有 Null 值。
  • flink查看taskManager日志
  • 数组内存学习
  • Django5.1(130)—— 表单 API一(API参考)
  • 【Luogu】每日一题——Day14. P5960 【模板】差分约束 (图论 + 最短路)
  • Nacos-服务注册,服务发现(二)
  • mac版SVN客户端
  • Pythong高级入门Day5
  • npm ERR! cb() never called!
  • 昇思学习营-DeepSeek-R1-Distill-Qwen-1.5B 模型LoRA微调学习心得
  • 数据结构 二叉树(2)---二叉树的实现
  • 2025年SEVC SCI2区,混沌编码量子粒子群算法QPSO+柔性车间调度,深度解析+性能实测
  • 每日一题7.26
  • WorkManager vs Flow 适用场景分析
  • 抖音短视频矩阵系统源码搭建---底层框架5年开发分享
  • idea中无法删除模块,只能remove?
  • 二叉搜索树(Binary Search Tree)详解与java实现
  • 代码随想录打卡第十五天
  • 睡眠函数 Sleep() C语言
  • AI Agent开发学习系列 - langchain: 本地大模型调用
  • CMU15445-2024fall-project4踩坑经历
  • 设计自己的小传输协议 状态机解析与封装抽象