原子操作(C++)

原子操作

  • [一、原子操作与 C++ 内存模型概述](#一、原子操作与 C++ 内存模型概述)
  • [二、std::atomic 基本用法(C++11--17)](#二、std::atomic 基本用法(C++11–17))
    • [1. 头文件与模板](#1. 头文件与模板)
    • [2. Lock‐free 保证](#2. Lock‐free 保证)
    • [3. 常用操作接口](#3. 常用操作接口)
      • [1. store / load](#1. store / load)
      • [2. exchange](#2. exchange)
      • [3. compare_exchange_weak / compare_exchange_strong](#3. compare_exchange_weak / compare_exchange_strong)
      • [4. fetch_add / fetch_sub / fetch_and / fetch_or / fetch_xor](#4. fetch_add / fetch_sub / fetch_and / fetch_or / fetch_xor)
      • [5. atomic_thread_fence](#5. atomic_thread_fence)
      • [6. std::atomic_flag 的专用操作](#6. std::atomic_flag 的专用操作)
      • 小结
  • [四、C++17 中的改进点](#四、C++17 中的改进点)
  • [五、C++20/23 的新特性](#五、C++20/23 的新特性)
    • [1. std::atomic_ref(C++20)](#1. std::atomic_ref(C++20))
    • [2. 原子等待/通知(C++20)](#2. 原子等待/通知(C++20))
    • [3. 并发构件(C++20/23,<latch>, <barrier>, <semaphore>)](#3. 并发构件(C++20/23,<latch>, <barrier>, <semaphore>))
    • [4. 其他细节](#4. 其他细节)
  • 六、使用建议与注意事项

一、原子操作与 C++ 内存模型概述

  • 原子性 (Atomicity)

    原子操作在多线程并发下要么全部完成,要么全部不做,不会被线程调度中断。

  • 可见性 (Visibility)

    一个线程对内存的写入,如果不通过原子操作或合适的内存屏障,其他线程可能无法及时看到。

  • 有序性 (Ordering)

    在乱序执行的 CPU 与优化过的编译器下,指令执行与内存访问可能与源代码顺序不一致,需通过内存序(memory order)来控制。

C++ 自 C++11 起定义了正式的跨平台内存模型,彻底解决了 "数据竞争" 问题。所有而非原子操作的并发读/写同一内存区都属于未定义行为。

二、std::atomic 基本用法(C++11--17)

1. 头文件与模板

cpp 复制代码
#include <atomic>

// 常用特化
std::atomic<int>    a_int;
std::atomic<bool>   a_bool;
std::atomic<void*>  a_ptr;
  • 对内置整型、指针、bool 等都有专门的特化。

  • std::atomic 仅对"满足TriviallyCopyable"的类型有效。

2. Lock‐free 保证

  • a.is_lock_free():运行时查询该原子对象在当前平台是否无锁实现。

  • std::atomic::is_always_lock_free (constexpr,自 C++17):编译期常量,指示在所有平台上是否都无锁。

cpp 复制代码
static_assert(std::atomic<int>::is_always_lock_free, "int should be lock-free");

3. 常用操作接口

操作 原型 说明
store(v, mo) void store(T v, memory_order mo = memory_order_seq_cst) 原子写
load(mo) T load(memory_order mo = memory_order_seq_cst) 原子读
exchange(v, mo) T exchange(T v, memory_order mo = memory_order_seq_cst) 交换并返回旧值
compare_exchange_weak/strong(expected, desired, mo...) bool compare_exchange_weak(T& expected, T desired, memory_order...); CAS 操作,失败时 expected 更新为实际值
fetch_add/sub/and/or/xor T fetch_add(T v, memory_order mo = ...) 原子加减与位运算
atomic_thread_fence(mo) void atomic_thread_fence(memory_order mo) 全局内存栅栏
注:所有接口可指定 memory_order,详见下节。

1. store / load

函数原型

cpp 复制代码
void store(T desired,
           std::memory_order mo = std::memory_order_seq_cst) noexcept;
T    load(std::memory_order mo = std::memory_order_seq_cst) const noexcept;

语义

  • store:将原子对象的值写为 desired。

  • load:原子地读取当前值并返回。

默认内存序

  • seq_cst:全局顺序,最严格。
示例 复制代码
cpp
std::atomic<int> a{0};
// 写入
a.store(42);  // 等价于 a.store(42, memory_order_seq_cst);
// 读取
int v = a.load();  // v == 42

注意

  • memory_order_relaxed 下,只保证原子性,不保证可见性或顺序。

  • memory_order_release 仅对 store 有效;memory_order_acquire 仅对 load 有效。

2. exchange

函数原型

cpp 复制代码
T exchange(T desired,
           std::memory_order mo = std::memory_order_seq_cst) noexcept;

语义

  • 将原子对象的值替换为 desired,返回替换前的旧值。

  • 原子地执行交换操作。

示例

cpp 复制代码
std::atomic<int> a{5};
int old = a.exchange(10);  // old == 5, a == 10

用途

  • "取旧值并写新值" 场景,如实现简单的互斥标志。

3. compare_exchange_weak / compare_exchange_strong

函数原型

cpp 复制代码
bool compare_exchange_weak(T& expected, T desired,
                           std::memory_order success = std::memory_order_seq_cst,
                           std::memory_order failure = std::memory_order_seq_cst) noexcept;

bool compare_exchange_strong(T& expected, T desired,
                             std::memory_order success = std::memory_order_seq_cst,
                             std::memory_order failure = std::memory_order_seq_cst) noexcept;

语义

  • CAS(Compare‐And‐Swap):如果当前值等于 expected,则原子地写入 desired 并返回 true;否则将当前值载入 expected 并返回 false。

区别:

  • weak 可能 伪失败(spuriously fail)------常用于自旋循环,可提高性能。

  • strong 保证只有在值不匹配时才失败。

示例

cpp 复制代码
std::atomic<int> a{0};
int expected = 0;
bool ok = a.compare_exchange_weak(expected, 1);
// 如果 a == 0,则写入 1 并 ok==true;
// 否则 expected 被赋为 a 的当前值,ok==false。

4. fetch_add / fetch_sub / fetch_and / fetch_or / fetch_xor

函数原型

cpp 复制代码
T fetch_add(T arg,
            std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_sub(T arg,
            std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_and(T arg,
            std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_or (T arg,
            std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_xor(T arg,
            std::memory_order mo = std::memory_order_seq_cst) noexcept;

语义

对当前值执行指定的算术或按位运算,返回 原子操作前 的旧值。

示例

cpp 复制代码
复制
编辑
std::atomic<int> counter{0};
// 累加
int prev = counter.fetch_add(3); // prev==0, counter==3
// 按位或
std::atomic<int> flags{0b0011};
int oldf = flags.fetch_or(0b1100); // oldf==0b0011, flags==0b1111

5. atomic_thread_fence

函数原型

cpp 复制代码
void atomic_thread_fence(std::memory_order mo) noexcept;

语义

  • 在调用位置插入 全局内存栅栏。

  • 保证在栅栏前后的非原子访问遵循指定的排序语义。

示例

cpp 复制代码
int data = 0;
std::atomic<bool> ready{false};

// 写线程
data = 42;
atomic_thread_fence(std::memory_order_release);
ready.store(true, std::memory_order_relaxed);

// 读线程
if (ready.load(std::memory_order_relaxed)) {
    atomic_thread_fence(std::memory_order_acquire);
    // 这里一定能看到 data==42
}

6. std::atomic_flag 的专用操作

函数原型

cpp 复制代码
std::atomic_flag flag = ATOMIC_FLAG_INIT;
bool test_and_set(std::memory_order mo = std::memory_order_seq_cst) noexcept;
void clear(std::memory_order mo = std::memory_order_seq_cst) noexcept;

语义

  • test_and_set:原子地将标志置1,返回先前值。

  • clear:将标志置0。

示例(自旋锁) 复制代码
cpp
std::atomic_flag lock = ATOMIC_FLAG_INIT;

void acquire() {
    while (lock.test_and_set(std::memory_order_acquire)) {
        ; // spin
    }
}
void release() {
    lock.clear(std::memory_order_release);
}

小结

  • 选择接口:简洁的读写用 load/store;需要"交换"用 exchange;需要条件更新用 CAS;计数与标志位操作用 fetch_*;全局栅栏用 atomic_thread_fence;轻量锁用 atomic_flag。

  • 内存序权衡:默认 seq_cst 最安全;对性能敏感时可以降级到 acquire/release 或 relaxed。

  • 性能注意:

    • CAS 自旋要配合弱版(weak)使用,并写成循环;

    • 避免在大对象上使用原子;

    • 对简单标志推荐使用 atomic_flag。

四、C++17 中的改进点

  • is_always_lock_free 成为 constexpr

    便于在编译期静态断言 lock‐free 保证。

  • std::atomic 的 constexpr 构造

    从 C++17 起,原子对象可在常量表达式中初始化:

cpp 复制代码
constexpr std::atomic<int> a{0};
  • 内存模型与编译器优化的细微修正
    标准更精确地界定了 consume 的含义(尽管大部分实现依然把它当成 acquire 来处理)。

五、C++20/23 的新特性

1. std::atomic_ref(C++20)

对已有对象提供原子视图,无需把原类型改为 std::atomic:

cpp 复制代码
int  counter = 0;
std::atomic_ref<int> a(counter);
a.fetch_add(1, std::memory_order_acq_rel);
// counter 也随之变化
  • 语义与 std::atomic 基本一致,但不占用额外内存。

2. 原子等待/通知(C++20)

  • wait/notify_one/notify_all:在整型或指针类型上直接阻塞与唤醒,替代条件变量的部分场景。
cpp 复制代码
std::atomic<int> flag{0};

// 等待线程
if (flag.load() != 1)
    flag.wait(0);  // 阻塞直到不等于0

// 唤醒线程
flag.store(1);
flag.notify_one();

3. 并发构件(C++20/23,, , )

  • 虽不属于原子模板,但与原子操作密切相关,提供更高层的同步原语。

4. 其他细节

  • 在 C++23 中,std::atomic_flag::wait/notify_* 同步功能完善。

  • 针对 shared_ptr、unique_ptr 的原子支持仍在并行标准/TS 中演进。

六、使用建议与注意事项

  • 只在真正需要无锁同步时使用原子操作,不宜滥用;

  • 对复杂同步场景,优先考虑 mutex+condition_variable 或并发算法库;

  • 精确选择内存序:默认 seq_cst 最安全,对性能敏感时再切换到更松的语义;

  • 警惕 memory_order_consume 在主流编译器上的支持不足。

相关推荐
linff9119 分钟前
Reactor和Proactor
c++·网络编程’
末日汐1 小时前
STL-list
开发语言·c++
qq_433554541 小时前
C++ list数据删除、list数据访问、list反转链表、list数据排序
开发语言·c++·list
uyeonashi1 小时前
【从零开始学习QT】快捷键、帮助文档、Qt窗口坐标体系
开发语言·c++·qt·学习
愚润求学2 小时前
【Linux】mmap文件内存映射
linux·运维·服务器·开发语言·c++
乌鸦9443 小时前
《STL--stack 和 queue 的使用及其底层实现》
开发语言·c++·priority_queue·适配器stack、queue
@我漫长的孤独流浪3 小时前
数据结构测试模拟题(2)
数据结构·c++·算法
xtmatao4 小时前
WIN11+VSCODE搭建c/c++开发环境
c语言·c++·vscode
一只自律的鸡4 小时前
STL之vector
开发语言·c++·算法
梁下轻语的秋缘4 小时前
每日c/c++题 备战蓝桥杯(P2240 【深基12.例1】部分背包问题)
c语言·c++·蓝桥杯