什么是原子操作?
原子操作,就是一个不可中断的操作,它要么完整地执行完毕,要么完全不执行,中间不会被任何其他线程或中断打断,在多线程编程中,原子操作是确保共享数据安全访问和修改的基石,因为它可以避免竞态条件。

一个经典的例子是 count++ 操作,在高层次上,它看起来是一个单一操作,但在底层,它通常至少包含三个步骤:
- 读取
count的当前值到寄存器。 - 增加 寄存器中的值。
- 写回 寄存器中的新值到
count的内存地址。
如果没有原子性,在多线程环境下,可能会发生以下情况:
- 线程A读取
count(值为5)。 - 线程B读取
count(值也为5)。 - 线程A将寄存器中的值加1,变为6,并写回
count。 - 线程B也将寄存器中的值加1,变为6,并写回
count。
count 的值是6,而不是预期的7,这就是一个典型的竞态条件。
原子操作通过硬件指令(如 x86 的 LOCK 前缀指令)或操作系统/编译器的内部机制来保证这三个步骤作为一个整体执行,从而避免上述问题。

C11 标准中的原子操作
C11 标准在 <stdatomic.h> 头文件中引入了一套完整的原子操作API,这是在现代C语言中进行无锁编程的标准方法。
1 核心概念
_Atomic类型限定符:你可以用它来声明一个原子变量。_Atomic int atomic_counter; // 或者使用 typedef typedef _Atomic int atomic_int; atomic_int atomic_counter;
atomic_flag:一个最简单的原子布尔类型,它只支持test-and-set和clear操作,非常适合用作自旋锁。memory_order:内存序,它定义了原子操作与内存操作之间的可见性和顺序关系,这是原子操作中最复杂也最重要的部分。
2 内存序
内存序决定了其他线程何时能看到一个原子变量的修改,以及编译器和CPU如何重排指令。
C11 定义了六种内存序:
| 内存序 | 描述 | 适用场景 |
|---|---|---|
memory_order_relaxed |
最宽松,只保证原子操作本身的原子性,不保证任何顺序。 | 简单的计数器,不依赖其他变量的顺序。 |
memory_order_consume |
当前线程依赖该原子变量的值(数据依赖)的后续读取操作,不能被重排到该原子操作之前。 | 较少使用,memory_order_acquire 通常是更好的选择。 |
memory_order_acquire |
获取,保证在该原子操作之后的所有读/写操作,都不能被重排到该原子操作之前。 | 用于加锁或获取数据,确保你看到的是最新的、完整的数据。 |
memory_order_release |
释放,保证在该原子操作之前的所有读/写操作,都不能被重排到该原子操作之后。 | 用于解锁或发布数据,确保所有修改对其他线程可见。 |
memory_order_acq_rel |
获取和释放,结合了 acquire 和 release 的语义。 |
在读取和写入都发生的原子操作中使用(如 fetch_add)。 |
memory_order_seq_cst |
顺序一致性。最严格,所有线程都按照一个统一的、全局的顺序来观察所有原子操作,默认选项,但性能开销最大。 | 需要绝对确定性的行为,不关心性能的场景。 |
重要提示:如果你不确定使用哪种内存序,memory_order_seq_cst 是最安全的选择,如果你追求性能,则需要仔细理解并选择合适的序。

C11 原子操作函数详解
<stdatomic.h> 提供了一系列宏函数来操作原子变量,这些函数通常命名为 atomic_...。
1 基本操作
假设我们有一个原子变量 atomic_int my_var;
| 函数 | 描述 | 示例 |
|---|---|---|
atomic_init |
静态初始化原子变量。 | atomic_int my_var = ATOMIC_VAR_INIT(10); |
atomic_load |
原子地读取一个值。 | int val = atomic_load(&my_var); |
atomic_store |
原子地存储一个值。 | atomic_store(&my_var, 20); |
atomic_exchange |
原子地交换一个新值,并返回旧值。 | int old_val = atomic_exchange(&my_var, 30); |
2 "Fetch-Op" 模式
这类函数先获取旧值,然后执行一个操作(如加、与、或等),最后存储新值,并返回旧值,这是最常用的模式。
| 函数 | 描述 | 示例 |
|---|---|---|
atomic_fetch_add |
原子地加一个值。 | atomic_fetch_add(&my_var, 1); // my_var += 1 |
atomic_fetch_sub |
原子地减一个值。 | atomic_fetch_sub(&my_var, 1); // my_var -= 1 |
atomic_fetch_and |
原子地执行按位与。 | atomic_fetch_and(&my_var, 0xFF); |
atomic_fetch_or |
原子地执行按位或。 | atomic_fetch_or(&my_var, 0x01); |
atomic_fetch_xor |
原子地执行按位异或。 | atomic_fetch_xor(&my_var, 0x01); |
atomic_fetch_min |
原子地取最小值。 | atomic_fetch_min(&my_var, 100); |
atomic_fetch_max |
原子地取最大值。 | atomic_fetch_max(&my_var, 100); |
所有 fetch-op 函数都默认使用 memory_order_seq_cst,但可以指定内存序:
atomic_fetch_add_explicit(&my_var, 1, memory_order_relaxed);
3 比较并交换
这是原子操作中最强大的指令,是实现无锁数据结构(如无锁队列、栈)的核心。
atomic_compare_exchange_strong 和 atomic_compare_exchange_weak
这两个函数的作用是:“如果当前值等于预期值,就用新值替换它”。
strong版本:保证在值不等于预期值时绝对不会“伪失败”(spuriously fail),除非真的被其他线程修改了,否则不会返回 false。weak版本:可能会“伪失败”,即使值没有改变也可能返回 false,但它的性能通常更好,因为某些硬件实现上weak版本更高效。
使用模式(关键!):CAS 操作通常在一个循环中使用。
int expected = 10;
int new_val = 20;
// 尝试将 my_var 从 10 变为 20
while (!atomic_compare_exchange_strong(&my_var, &expected, new_val)) {
// 如果失败,说明 my_var 的值已经被其他线程修改了。
// expected 的值已经被自动更新为 my_var 的当前值。
// 我们需要重新加载 my_var 的当前值到 expected 中,然后再次尝试。
// 注意:expected 必须是指向一个变量的指针,而不是字面量。
expected = atomic_load(&my_var); // 重新加载当前值
}
完整代码示例
下面是一个使用 C11 原子操作实现一个线程安全的计数器的例子。
示例1:简单的原子计数器
#include <stdio.h>
#include <stdatomic.h>
#include <threads.h>
#define NUM_THREADS 4
#define INCREMENTS_PER_THREAD 1000000
// 声明一个原子整数
atomic_int counter;
// 线程函数,对计数器进行递增
int thread_func(void* arg) {
for (int i = 0; i < INCREMENTS_PER_THREAD; i++) {
// 使用 memory_order_relaxed 因为我们只关心计数器的最终值,
// 不关心各个线程操作的相对顺序。
atomic_fetch_add(&counter, 1);
}
return 0;
}
int main() {
// 初始化计数器
atomic_init(&counter, 0);
thrd_t threads[NUM_THREADS];
// 创建并启动线程
for (int i = 0; i < NUM_THREADS; i++) {
if (thrd_create(&threads[i], thread_func, NULL) != thrd_success) {
perror("Failed to create thread");
return 1;
}
}
// 等待所有线程完成
for (int i = 0; i < NUM_THREADS; i++) {
thrd_join(threads[i], NULL);
}
// 打印最终结果
printf("Final counter value: %d\n", atomic_load(&counter));
// 预期结果是 NUM_THREADS * INCREMENTS_PER_THREAD
if (atomic_load(&counter) == NUM_THREADS * INCREMENTS_PER_THREAD) {
printf("Test passed!\n");
} else {
printf("Test failed!\n");
}
return 0;
}
示例2:使用 atomic_flag 实现自旋锁
atomic_flag 是一个简单的测试并置位标志,非常适合用作自旋锁。
#include <stdio.h>
#include <stdatomic.h>
#include <threads.h>
// atomic_flag 可以用 ATOMIC_FLAG_INIT 初始化为 false (未锁定)
atomic_flag spinlock = ATOMIC_FLAG_INIT;
// 受保护的共享资源
int shared_resource = 0;
void lock() {
// 尝试将 flag 设置为 true。
// flag 原本就是 true (已被锁定),则循环等待 (自旋)。
while (atomic_flag_test_and_set(&spinlock)) {
// 空循环,等待锁被释放
// 在实际应用中,可以考虑加入一些让渡 CPU 的指令,如 thrd_yield()
}
}
void unlock() {
// 将 flag 清除,释放锁
atomic_flag_clear(&spinlock);
}
int thread_func(void* arg) {
for (int i = 0; i < 100000; i++) {
lock();
// 临界区:安全地修改共享资源
shared_resource++;
unlock();
}
return 0;
}
int main() {
shared_resource = 0;
thrd_t t1, t2;
thrd_create(&t1, thread_func, NULL);
thrd_create(&t2, thread_func, NULL);
thrd_join(t1, NULL);
thrd_join(t2, NULL);
printf("Final shared_resource value: %d\n", shared_resource);
// 预期结果是 200000
if (shared_resource == 200000) {
printf("Lock test passed!\n");
}
return 0;
}
平台特定的原子操作
虽然 C11 是标准,但在某些追求极致性能的场景下,开发者可能会直接使用平台特定的原子指令,这些指令通常能提供更底层的控制和更好的性能。
-
GCC/Clang 内置函数:
__sync_fetch_and_add(&ptr, val)__sync_lock_test_and_set(&ptr, val)__atomic_fetch_add(&ptr, val, __ATOMIC_SEQ_CST)- GCC 也支持 C11 的
<stdatomic.h>,并且其__atomic_*内置函数是 C11 的超集。
-
MSVC 内置函数:
InterlockedIncrement((LONG volatile*)Addend)InterlockedExchange((LONG volatile*)Target, Value)InterlockedCompareExchange((LONG volatile*)Destination, Exchange, Comparand)
-
Windows API:
InterlockedIncrement,InterlockedDecrement,InterlockedExchange,InterlockedCompareExchange等。
-
x86 汇编指令:
LOCK XADD(交换并相加)LOCK CMPXCHG(比较并交换)
注意:使用平台特定代码会降低程序的可移植性,除非你明确知道目标平台并且对性能有极致要求,否则优先使用 C11 标准。
| 方法 | 优点 | 缺点 |
|---|---|---|
C11 <stdatomic.h> |
标准、可移植、高级抽象,编译器会自动选择最优的底层实现。 | 内存序概念复杂,性能可能略低于平台特定代码(但通常差异不大)。 |
| 平台特定/GCC内置 | 性能极高,提供最底层的控制。 | 代码不可移植,可读性差,容易出错。 |
对于绝大多数C多线程应用,强烈推荐从 C11 的原子操作开始,它们提供了良好的性能、可移植性和安全性,是现代C语言并发编程的正确方向。
