Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

1. 核心概念和实现原理

1.1. 什么是原子操作?

原子变量(std::atomic)是 C++11 引入的多线程同步原语,它能保证对变量的单个操作是不可分割的——要么完整执行,要么完全没执行,中间不会被其他线程打断,从而避免数据竞争(data race)。

1.2. 为什么需要原子操作?

考虑一个简单的场景:多个线程同时对一个整数执行自增操作:

1
2
3
int counter = 0;
// 线程1: counter++;
// 线程2: counter++;

看似简单的一行 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
#include <atomic>

2.1. 类型定义

C++提供了两种方式来定义原子变量:

2.1.1. 方式一:使用 std::atomic<T> 模板

这是最通用的方式,T 可以是任何可平凡复制(trivially copyable)的类型:

1
2
3
4
std::atomic<int> counter;           // 原子整数
std::atomic<bool> flag; // 原子布尔
std::atomic<double> value; // 原子浮点数
std::atomic<char*> ptr; // 原子指针

自定义结构体也可以作为原子类型:

1
2
3
4
5
6
struct Point {
int x;
int y;
};
std::atomic<Point> atomic_point; // 原子结构体
// 原子结构体要求类型是平凡可复制的,且大小通常不超过平台指针宽度

2.1.2. 方式二:使用预定义的类型别名

C++为基本类型提供了便捷的别名:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::atomic_int counter;    // 等价于 std::atomic<int>
std::atomic_bool flag; // 等价于 std::atomic<bool>
std::atomic_char ch; // 等价于 std::atomic<char>
std::atomic_long long_num; // 等价于 std::atomic<long>

std::atomic_int8_t i8; // 等价于 std::atomic<int8_t>
std::atomic_int16_t i16; // 等价于 std::atomic<int16_t>
std::atomic_int32_t i32; // 等价于 std::atomic<int32_t>
std::atomic_int64_t i64; // 等价于 std::atomic<int64_t>
std::atomic_uint8_t u8; // 等价于 std::atomic<uint8_t>
std::atomic_uint16_t u16; // 等价于 std::atomic<uint16_t>
std::atomic_uint32_t u32; // 等价于 std::atomic<uint32_t>
std::atomic_uint64_t u64; // 等价于 std::atomic<uint64_t>

2.1.3. 特殊类型:std::atomic_flag

std::atomic_flag 是一个特殊的原子布尔类型,保证是无锁的(lock-free)。它不提供 loadstore 操作,只提供 test_and_setclear 操作,非常适合实现自旋锁等底层同步原语。

1
std::atomic_flag lock = ATOMIC_FLAG_INIT;  // 初始化为 false

2.2. 变量初始化

原子变量的初始化本身不是原子操作,因此必须在多线程启动之前完成。

2.2.1. 方法一:直接初始化(推荐)

1
2
3
std::atomic<int> counter(0);        // 初始化为0
std::atomic<bool> ready(true); // 初始化为true
std::atomic<int> value{42}; // 使用列表初始化

2.2.2. 方法二:默认构造后赋值

1
2
std::atomic<int> counter;           // 默认构造,未初始化
counter = 10; // 赋值初始化(原子操作)

注意:默认构造函数不会初始化底层值,除了静态和线程局部对象会被零初始化。

2.2.3. 方法三:使用 ATOMIC_VAR_INIT 宏(C风格)

主要用于C兼容的静态初始化:

1
std::atomic<int> counter = ATOMIC_VAR_INIT(0);

2.2.4. 方法四:使用 std::atomic_init

用于延迟初始化一个已经默认构造的原子变量:

1
2
std::atomic<int> counter;
std::atomic_init(&counter, 42); // 延迟初始化

3. 基本操作

3.1. 核心操作(存储、加载与交换)

3.1.1. store —— 存储(写入)

将一个新值存储到原子变量中:

1
2
std::atomic<int> counter(0);
counter.store(10); // 原子地将 counter 设为 10

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <atomic>
#include <iostream>

int main() {
std::atomic<int> counter(0);

counter.store(10); // 存储
int value = counter.load(); // 加载
std::cout << "Counter: " << value << std::endl; // 输出: Counter: 10

int old = counter.exchange(20); // 交换
std::cout << "Old: " << old << ", New: " << counter.load() << std::endl;
// 输出: Old: 10, New: 20

return 0;
}

3.2. 算术与位运算

C++原子变量提供了丰富的读-修改-写(Read-Modify-Write)操作:

3.2.1. 算术运算

1
2
3
4
5
6
7
8
std::atomic<int> num(0);

num.fetch_add(5); // 原子地执行 num += 5,返回旧值, num 为5
num.fetch_sub(3); // 原子地执行 num -= 3,返回旧值, num 为2
num++; // 原子地执行自增(后缀),返回旧值, num 为3
++num; // 原子地执行自增(前缀),返回新值, num 为4
num--; // 原子地执行自减(后缀),返回旧值, num 为3
--num; // 原子地执行自减(前缀),返回新值, num 为2

3.2.2. 位运算

1
2
3
4
5
std::atomic<int> flags(0b1010);

flags.fetch_or(0b0100); // 原子地执行 flags |= 0b0100
flags.fetch_and(0b1111); // 原子地执行 flags &= 0b1111
flags.fetch_xor(0b0010); // 原子地执行 flags ^= 0b0010

3.2.3. 比较交换(CAS —— Compare-And-Swap)

CAS是构建无锁数据结构的核心操作:

1
2
3
4
5
6
7
8
9
10
11
12
std::atomic<int> value(10);
int expected = 10;

// 如果 value == expected,则将 value 设为 15,返回 true
// 否则将 expected 设为 value 的实际值,返回 false
bool success = value.compare_exchange_strong(expected, 15);

if (success) {
std::cout << "CAS成功,value = " << value.load() << std::endl;
} else {
std::cout << "CAS失败,expected 被更新为 " << expected << std::endl;
}

compare_exchange_weakcompare_exchange_strong 的区别在于:弱版本在某些平台上可能会伪失败(即使值相等也返回false),通常用在循环中:

1
2
3
4
int expected = 10;
while (!value.compare_exchange_weak(expected, 15)) {
// 循环重试,expected 已被更新为当前值
}

3.3. 标志类型专用

std::atomic_flag 是专门为低层同步设计的,只提供两个操作:

3.3.1. test_and_set —— 测试并设置

原子地将标志设为 true,并返回之前的值:

1
2
3
4
5
6
std::atomic_flag lock = ATOMIC_FLAG_INIT;

bool was_set = lock.test_and_set(); // 设为true,返回之前的值
if (!was_set) {
// 之前是false,说明我们成功获取了锁
}

3.3.2. clear —— 清除

原子地将标志设为 false

1
lock.clear();  // 释放锁

3.3.3. 实战:用 atomic_flag 实现自旋锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <atomic>
#include <thread>

class SpinLock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;

public:
void lock() {
// 自旋等待,直到成功获取锁
while (flag.test_and_set()) {
// 忙等待
}
}

void unlock() {
flag.clear(); // 释放锁
}
};

// 使用示例
SpinLock spin_lock;
int shared_data = 0;

void worker() {
spin_lock.lock();
shared_data++; // 临界区
spin_lock.unlock();
}

评论