std::atomic

是 C++ 并发支持库提供的原子类型模板
其核心目标是:

  • 在多线程环境中,对共享对象的并发读写提供良定义行为
  • 避免数据竞争(data race)导致的未定义行为;
  • 通过 std::memory_order 指定线程间同步关系与内存访问顺序

若一个线程写入某个 atomic 对象,另一个线程并发读取该对象,则程序行为是良定义的。

模板定义

template< class T >  
struct atomic;     // since C++11
template< class U >  
struct atomic<U*>; // since C++11
template< class U >  
struct atomic<std::shared_ptr<U>>; // since C++20
template< class U >  
struct atomic<std::weak_ptr<U>>; // since C++20
主模版实例化条件

std::atomic<T> 的主模板 并非对任意类型开放
当且仅当以下条件全部满足时,程序才是良构的:

  • TTriviallyCopyable
  • T 可拷贝构造、可移动构造
  • T 可拷贝赋值、可移动赋值
  • T 不带 const / volatile 修饰
Trivially Copyable

可平凡复制 是 C++ 类型的一种属性,指该类型的对象可以通过简单的内存拷贝(如 memcpy) 完成复制,且复制后的对象与原对象完全等价、行为正常
满足 Trivially Copyable 的条件

  • 没有自定义的复制 / 移动构造函数、复制 / 移动赋值运算符
  • 没有自定义的析构函数
  • 所有非静态成员变量都是 Trivially Copyable 类型
  • 没有虚函数、没有虚基类
    Trivially Copyable 类型的对象,不只是一段纯数据,还包含复杂的 “语义” 和 “资源关联”。memcpy 只会复制字节,不会处理这些复杂语义,直接导致未定义行为

内建类型特化

整数类型特化

T 为标准整数类型或 <cstdint> 中定义的整数类型时,std::atomic<T> 额外提供:

  • 原子算术:fetch_add, fetch_sub
  • 原子位运算:fetch_and, fetch_or, fetch_xor
  • 自增 / 自减运算符
  • 对应的复合赋值运算符
浮点类型特化(C++20)

Tfloatdoublelong double 或扩展浮点类型时:

  • 提供 fetch_add / fetch_sub
  • 即使结果不可表示,也不产生未定义行为;
指针特化

std::atomic<U*> 提供:

  • 指针原子读写;
  • 按元素单位的指针算术原子操作;
  • difference_type 定义为 std::ptrdiff_t
成员类型(Member types)
成员类型 适用范围
value_type 所有 std::atomic<T>,定义为 T
difference_type 仅存在于整数、浮点、指针特化

主模板、自定义类型、shared_ptr / weak_ptr 特化 不定义 difference_type

函数

通用函数
构造函数

提供两种核心构造方式,拷贝构造函数被显式删除
为避免并发语义被破坏,std::atomic 被设计为不可拷贝、不可移动

constexpr atomic() noexcept; // 默认构造,处于未指定状态,先显式赋值
constexpr atomic( T desired ) noexcept;
赋值运算符

重载了底层值类型的赋值运算符,赋值操作等价于调用 store 函数,同样拷贝赋值运算符被删除

T operator=( T desired ) noexcept;

赋值运算符仅支持底层值类型, 不支持原子类型之间的赋值

is_lock_free

判断当前原子对象的操作是否为无锁实现

  • 无锁(lock-free):直接通过硬件指令(如 CPU 的原子指令)实现,性能极高,无线程阻塞。
  • 有锁(non-lock-free):可能通过库/运行时回退机制实现,不保证无阻塞。
// 成员函数:运行时判断当前对象是否无锁
bool is_lock_free() const noexcept;

// C++17 新加静态成员常量
static constexpr bool is_always_lock_free = /* 实现定义 */;

return:

  • true:操作是无锁实现。
  • false:操作是有锁实现。
store

将指定的值原子地存储std::atomic 对象中,覆盖原有值,操作不可分割。

void store( T desired, std::memory_order order = std::memory_order_seq_cst ) const noexcept;
  • order:内存序,控制操作的内存可见性,默认 std::memory_order_seq_cst(最强内存序,保证全局顺序一致)。
load

原子地读取 std::atomic 对象的当前值,返回读取到的值,操作不可分割。

T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept;
operator T() const noexcept;

代码示例

#include <atomic>
#include <iostream>
int main() {
    std::atomic<int> a(100);
    // 显式调用 load()
    int val1 = a.load();
    // 隐式转换,等价于 a.load()
    int val2 = a;

    std::cout << val1 << " " << val2 << std::endl; // 输出 100 100
    return 0;
}
exchange

原子地执行 “读旧值 + 写新值”:将新值写入原子对象,同时返回写入前的旧值。整个操作不可分割,是 “先读后写” 的原子化实现。

T exchange( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;

代码示例

#include <atomic>
#include <iostream>
int main() {
    std::atomic<int> a(10);
    int old_val = a.exchange(20);
    std::cout << "旧值:" << old_val << ",新值:" << a.load() << "\n"; // 旧值:10,新值:20
    return 0;
}
CAS

compare_exchange_weak / compare_exchange_strong
这两个函数是 CAS(Compare-And-Swap,比较并交换) 操作,是实现无锁数据结构(如无锁队列、无锁栈)的核心,也是最复杂的原子函数。

通用 CAS 原理

两个函数的核心逻辑完全一致,原子地执行以下步骤:

  1. 比较原子对象的当前值expected(预期值)。
  2. 如果相等:将原子对象的值替换为 desired,返回 true
  3. 如果不相等:将原子对象的当前值写入 expected 变量,返回 false
    整个过程不可分割,是 “比较 - 替换” 的原子化实现。
语法
// 指定成功/失败的内存序
bool compare_exchange_weak( T& expected, T desired,
                            std::memory_order success,
                            std::memory_order failure ) noexcept;
// 失败内存序自动推导
bool compare_exchange_weak( T& expected, T desired,
                            std::memory_order order = std::memory_order_seq_cst ) noexcept;

bool compare_exchange_strong( T& expected, T desired,
                              std::memory_order success,
                              std::memory_order failure ) noexcept;
bool compare_exchange_strong( T& expected, T desired,
                              std::memory_order order = std::memory_order_seq_cst ) noexcept;
区别
特性 compare_exchange_weak compare_exchange_strong
伪失败 可能发生 绝不会发生
循环要求 必须配合循环使用 可单次使用,无需循环
性能 更高(多核平台优势明显) 稍低(额外硬件检查开销)
适用场景 循环重试的无锁操作(主流) 单次尝试的一次性操作
代码示例
void inc_bad() { 
	for (int i = 0; i < 100000; ++i) { 
		int old = counter.load(std::memory_order_relaxed); 
		counter.store(old + 1, std::memory_order_relaxed); 
		// 读写间,可能存在插入
		} 
	}

void inc_cas() {
    for (int i = 0; i < 100000; ++i) {
        int expected = counter.load(std::memory_order_relaxed);
        while (!counter.compare_exchange_weak(
            expected, expected + 1,
            std::memory_order_relaxed,
            std::memory_order_relaxed
        )) {
            // expected 在失败时会被写成“当前值”,继续尝试即可
        }
    }
}
特化成员函数
fetch_add / fetch_sub

整数、浮点、指针均支持

T fetch_add( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;

T fetch_sub( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
  • 参数 val:要加减的数值(整数 / 浮点)或偏移量(指针);
  • 参数 order:内存序,默认全局顺序一致;
  • 返回值:操作前的旧值(不是新值)。
++ / --

仅整数类型和指针类型支持 ++/-- 运算符重载.
本质是 fetch_add(1)/fetch_sub(1) 的语法糖,行为和普通 ++/-- 一致,但均为原子操作。

运算符 作用 返回值 等价操作
++atomic 前置自增(先加,后返回) 新值(引用) atomic.fetch_add(1) + 1
atomic++ 后置自增(先返回,后加) 旧值 atomic.fetch_add(1)
--atomic 前置自减(先减,后返回) 新值(引用) atomic.fetch_sub(1) - 1
atomic-- 后置自减(先返回,后减) 旧值 atomic.fetch_sub(1)
fetch_andfetch_orfetch_xor

仅整数类型(如 int/unsigned int/uint64_t)支持位运算特化函数,分别对应原子按位与、原子按位或、原子按位异或,是实现原子标志位、掩码操作的核心。

T fetch_and( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
T fetch_or( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
T fetch_xor( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
  • 参数 val:位运算的掩码值;
  • 返回值:操作前的旧值
std::memory_order

用于约束原子操作与其他内存访问之间的可见性与顺序关系。
用来在“没有 mutex 的情况下”,精确描述“哪些写对哪些读可见”。

内存模型中的三个核心概念
Modification Order(修改序)
  • 同一个 atomic 对象的所有写操作,所有线程观察到的顺序是一致的
  • 即使使用 memory_order_relaxed,这一点仍然成立

这保证了 atomic 不会出现“不同线程看到完全不一致的值历史”

Happens-Before

happens-before 是 C++ 内存模型中唯一能保证可见性的关系
若 A happens-before B,则:

  • A 的所有内存写入,对 B 必然可见
  • 编译器与 CPU 不得重排 A 到 B 之后
    memory_order 的本质作用:
    是否、以及如何建立 happens-before
Synchronizes-With

synchronizes-withhappens-before 的一种特殊来源,
最常见的就是:

store(memory_order_release)
load(memory_order_acquire)

std::memory_order 的枚举值
enum memory_order {
    memory_order_relaxed,
    memory_order_consume, // 基本弃用
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
};
memory_order_relaxed
  • 仅保证:
    • 原子性
    • 对同一 atomic 对象的修改序一致
  • 不建立任何 happens-before 关系
  • 不对普通内存访问施加顺序约束
memory_order_acquire
  • 本线程中:
    • acquire 之后 的所有读写
    • 不得被重排到 acquire 之前
  • 若读取到了由 release 写入的值:
    • 与该 release 建立 synchronizes-with
memory_order_release
  • 本线程中:
    • release 之前 的所有读写
    • 不得被重排到 release 之后
  • 若被 acquire 读取:
    • 与 acquire 建立同步
memory_order_acq_rel
  • 同时具备:
    • acquire(禁止向前重排)
    • release(禁止向后重排)
memory_order_seq_cst
  • 所有 seq_cst 原子操作:
    • 在全局上形成单一总序
  • 每个线程观察到的顺序一致
  • 同时具备 acquire + release 语义
示例
#include <atomic>
#include <thread>
#include <iostream>
#include <cassert>

struct Payload {
    int x;
    int y;
};

Payload g_data;                 // 普通共享数
std::atomic<bool> g_ready{false}; // 发布标志

void producer() {
    // 写入
    g_data.x = 42;
    g_data.y = 7;

    // 发布, release 保证之前的任何写入不会被重排到 store 之后
    g_ready.store(true, std::memory_order_release);
}

void consumer() {
    // 订阅。acquire 保证后续任何读取不会被重排到 load 之前
    while (!g_ready.load(std::memory_order_acquire)) {
        // ...
    }

    // 一旦读到 true,就“同步到” producer 的写入
    assert(g_data.x == 42);
    assert(g_data.y == 7);
    std::cout << "x=" << g_data.x << ", y=" << g_data.y << "\n";
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
}

发布–订阅模式里,原子变量通常不是“承载数据”,而是“承载同步”。数据放普通内存,通过 release/acquire 把整批写入一次性发布出去。
同时也不存在数据竞争

能力边界

std::atomic 提供的是单个对象级别的原子性与有限的线程间同步能力,用于在不引入互斥锁的情况下安全地读写共享状态。
通过合适的 std::memory_order,原子操作可以建立 happens-before 关系,从而实现发布–订阅等基础同步模式。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐