原子操作(Atomic Operation) 是指不可被中断的操作——要么完整执行,要么完全不执行
在计算机科学中,原子操作(Atomic Operation) 是指不可被中断的操作——要么完整执行,要么完全不执行,在操作过程中不会被其他线程或进程打断。这种特性确保了多线程环境下对共享资源的操作不会中间状态,从而避免竞态条件(Race Condition)。
核心特点
- 不可分割性:操作的所有步骤作为一个整体完成,不会被任何外部事件(如线程调度、中断)拆分。
- 无中间状态:其他线程只能看到操作执行前或执行后的状态,看不到执行过程中的临时状态。
- 硬件支持:通常由CPU的原子指令(如带锁前缀的汇编指令)提供底层支持,而非纯软件实现。
为什么需要原子操作?
在多线程场景中,看似简单的操作(如 count++
)实际包含多个步骤(读取→修改→写入),若被其他线程打断,会导致数据不一致:
int count = 0;// 线程1执行
count++; // 步骤1:读取count=0 → 步骤2:+1 → 步骤3:写入1// 线程2执行(若在线程1步骤1后、步骤3前被调度)
count++; // 同样读取到0,最终结果可能为1(错误),而非预期的2
原子操作通过将这三步合并为一个不可分割的指令,确保上述情况不会发生。
原子操作的实现方式
-
硬件层面:
CPU提供原子指令(如x86的LOCK
前缀指令),执行时会锁定总线或缓存,阻止其他CPU核心同时访问该内存地址。例如:LOCK INC [count]
:原子递增内存中的count
值。LOCK CMPXCHG
:原子比较并交换(Compare-and-Swap,CAS)操作。
-
软件层面:
编程语言或库通过封装硬件原子指令,提供高层接口(如C11的<stdatomic.h>
、C++的std::atomic
、Linux的atomic_t
)。
常见原子操作类型
-
原子读写:对变量的读取或写入操作是原子的(如32位整数的读写在多数CPU上天然是原子的)。
-
原子修改:
- 自增(
atomic_inc
)、自减(atomic_dec
) - 加法(
atomic_add
)、减法(atomic_sub
) - 比较并交换(CAS:Compare-And-Swap):若当前值等于预期值,则更新为新值,返回操作是否成功。
- 自增(
-
原子位操作:对变量的特定位进行原子置位、清零或翻转。
代码示例(C语言,使用C11标准)
#include <stdatomic.h>
#include <pthread.h>
#include <stdio.h>// 定义原子变量(int类型)
atomic_int count = 0;// 线程函数:原子递增count
void *thread_func(void *arg) {for (int i = 0; i < 10000; i++) {// 原子自增操作(不可分割)atomic_fetch_add(&count, 1);}return NULL;
}int main() {pthread_t tid1, tid2;pthread_create(&tid1, NULL, thread_func, NULL);pthread_create(&tid2, NULL, thread_func, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 输出结果一定是20000(无竞态条件)printf("count = %d\n", atomic_load(&count));return 0;
}
说明:
atomic_fetch_add
会将 count
原子递增1,替代了非原子的 count++
,确保两个线程并发操作的正确性。
原子操作 vs 互斥锁
特性 | 原子操作 | 互斥锁(Mutex) |
---|---|---|
粒度 | 单个变量的简单操作(如增减、赋值) | 任意代码块(临界区) |
性能 | 极高(仅需硬件指令,无上下文切换) | 较低(可能导致线程阻塞/唤醒) |
适用场景 | 简单数值操作(计数、标记位) | 复杂操作(如链表修改、多变量更新) |
实现依赖 | 硬件指令支持 | 操作系统或库实现 |
注意事项
- 适用范围有限:原子操作仅适用于简单的变量操作,无法保护复杂的代码块(如多步逻辑、链表操作)。
- 类型限制:通常只支持整数、指针等基本类型,不直接支持结构体等复杂类型。
- 内存顺序:高级原子操作需考虑内存可见性(如C11的内存序:
memory_order_seq_cst
、memory_order_acquire
等),确保多线程间的数据同步。
总结
原子操作是多线程编程中轻量级的同步机制,通过硬件保证的不可分割性,高效解决简单共享变量的并发访问问题。与互斥锁相比,它性能更高但适用场景更窄,常作为底层同步原语(如实现锁、信号量)或优化简单计数场景。