1. 核心概念和实现原理
1.1. 什么是原子操作?
原子变量(std::atomic)是 C++11 引入的多线程同步原语,它能保证对变量的单个操作是不可分割的——要么完整执行,要么完全没执行,中间不会被其他线程打断,从而避免数据竞争(data race)。
1.2. 为什么需要原子操作?
考虑一个简单的场景:多个线程同时对一个整数执行自增操作:
1 | int counter = 0; |
看似简单的一行 counter++,在底层实际上包含三个步骤:读取 → 修改 → 写入。当两个线程同时执行时,可能发生以下情况:
- 线程1读取到
counter = 0 - 线程2读取到
counter = 0 - 线程1写入
counter = 1 - 线程2写入
counter = 1
两次自增的结果本该是2,实际却变成了1——这就是竞态条件(Race Condition)。
原子操作确保这三个步骤作为一个整体不可分割地完成,从根本上杜绝了这类问题。
1.3. 实现原理
std::atomic 底层依赖于CPU提供的原子指令(如x86的 LOCK 前缀指令、ARM的 LDREX/STREX 指令对)。这些指令在硬件层面保证了操作的不可分割性。std::atomic 模板的每个实例化和完整特化都定义了一个原子类型。
这意味着:
- **无锁(Lock-Free)**:不需要操作系统介入,速度更快。
- 不可分割:操作要么全完成,要么全不完成。
- 线程安全:多线程同时读写不会导致数据竞争。
2. 基础定义与头文件
使用原子变量需要包含 <atomic> 头文件:
1 |
2.1. 类型定义
C++提供了两种方式来定义原子变量:
2.1.1. 方式一:使用 std::atomic<T> 模板
这是最通用的方式,T 可以是任何可平凡复制(trivially copyable)的类型:
1 | std::atomic<int> counter; // 原子整数 |
自定义结构体也可以作为原子类型:
1 | struct Point { |
2.1.2. 方式二:使用预定义的类型别名
C++为基本类型提供了便捷的别名:
1 | std::atomic_int counter; // 等价于 std::atomic<int> |
2.1.3. 特殊类型:std::atomic_flag
std::atomic_flag 是一个特殊的原子布尔类型,保证是无锁的(lock-free)。它不提供 load 或 store 操作,只提供 test_and_set 和 clear 操作,非常适合实现自旋锁等底层同步原语。
1 | std::atomic_flag lock = ATOMIC_FLAG_INIT; // 初始化为 false |
2.2. 变量初始化
原子变量的初始化本身不是原子操作,因此必须在多线程启动之前完成。
2.2.1. 方法一:直接初始化(推荐)
1 | std::atomic<int> counter(0); // 初始化为0 |
2.2.2. 方法二:默认构造后赋值
1 | std::atomic<int> counter; // 默认构造,未初始化 |
注意:默认构造函数不会初始化底层值,除了静态和线程局部对象会被零初始化。
2.2.3. 方法三:使用 ATOMIC_VAR_INIT 宏(C风格)
主要用于C兼容的静态初始化:
1 | std::atomic<int> counter = ATOMIC_VAR_INIT(0); |
2.2.4. 方法四:使用 std::atomic_init
用于延迟初始化一个已经默认构造的原子变量:
1 | std::atomic<int> counter; |
3. 基本操作
3.1. 核心操作(存储、加载与交换)
3.1.1. store —— 存储(写入)
将一个新值存储到原子变量中:
1 | std::atomic<int> counter(0); |
3.1.2. load —— 加载(读取)
原子地读取当前值:
1 | int value = counter.load(); // 原子地读取 counter 的值 |
3.1.3. exchange —— 交换
用新值替换原子变量的当前值,并返回旧值:
1 | int old_value = counter.exchange(20); // counter 变为20,old_value 为之前的值 |
3.1.4. 完整示例
1 |
|
3.2. 算术与位运算
C++原子变量提供了丰富的读-修改-写(Read-Modify-Write)操作:
3.2.1. 算术运算
1 | std::atomic<int> num(0); |
3.2.2. 位运算
1 | std::atomic<int> flags(0b1010); |
3.2.3. 比较交换(CAS —— Compare-And-Swap)
CAS是构建无锁数据结构的核心操作:
1 | std::atomic<int> value(10); |
compare_exchange_weak 与 compare_exchange_strong 的区别在于:弱版本在某些平台上可能会伪失败(即使值相等也返回false),通常用在循环中:
1 | int expected = 10; |
3.3. 标志类型专用
std::atomic_flag 是专门为低层同步设计的,只提供两个操作:
3.3.1. test_and_set —— 测试并设置
原子地将标志设为 true,并返回之前的值:
1 | std::atomic_flag lock = ATOMIC_FLAG_INIT; |
3.3.2. clear —— 清除
原子地将标志设为 false:
1 | lock.clear(); // 释放锁 |
3.3.3. 实战:用 atomic_flag 实现自旋锁
1 |
|