概念
原子操作指的是在执行过程中不会被任何其他操作中断的操作。在单核或多核处理器上,当多个线程或中断服务例程尝试同时访问和修改同一变量时,原子操作确保了这些操作是安全的,不会导致数据不一致。
为什么需要原子化访问
在多线程或多处理器环境中,如果多个执行单元同时访问共享资源(如全局变量),可能会发生以下问题:
竞态条件(Race Conditions):多个线程或中断试图同时读写同一变量,导致结果不可预测。 数据不一致(Data Inconsistency):一个线程的操作被另一个线程的操作打断,导致数据处于不一致的状态。原子化访问确保了在多线程或多处理器环境下对共享资源的访问是安全的。
原子操作的硬件支持
大多数现代处理器提供了以下硬件特性来支持原子操作:
原子指令:如x86架构的 XCHG
,LOCK
前缀指令,ARM架构的LDREX
和STREX
指令。内存屏障(Memory Barrier):确保内存操作的顺序,防止编译器和处理器重排指令。
C语言中的原子操作
C11标准引入了<stdatomic.h>
头文件,它提供了原子类型和操作函数。然而,并不是所有的编译器都支持C11标准,所以这里我们将使用GCC的内置函数来演示。
1. 原子自增操作
下面的例子展示了如何使用GCC的内置函数进行原子自增操作:
#include <stdio.h>
volatile int counter = 0;
void atomic_increment(void) {
__atomic_add_fetch(&counter, 1, __ATOMIC_SEQ_CST);
}
int main() {
atomic_increment();
atomic_increment();
printf("Counter: %d\n", counter); // 应该输出 2
return 0;
}
在上面的代码中,__atomic_add_fetch
是一个GCC内置函数,它原子地增加counter
的值并返回新的值。__ATOMIC_SEQ_CST
是内存顺序,确保最严格的顺序一致性。
2. 原子比较和交换操作
下面的例子展示了如何使用GCC的内置函数进行原子比较和交换操作:
#include <stdio.h>
volatile int value = 0;
int atomic_compare_and_swap(int *ptr, int oldval, int newval) {
return __atomic_compare_exchange_n(ptr, &oldval, newval, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}
int main() {
if (atomic_compare_and_swap(&value, 0, 1)) {
printf("Value was 0 and is now 1\n");
} else {
printf("Value was not 0\n");
}
return 0;
}
在上面的代码中,__atomic_compare_exchange_n
尝试原子地将ptr
指向的值从oldval
更改为newval
。如果成功,函数返回1,否则返回0。
注意事项
原子操作通常比非原子操作要慢,因为它们需要额外的硬件支持。 在单核处理器上,可以通过禁用中断来保证操作的原子性,但这在多核处理器上不可行。 在多线程或多处理器环境中,使用原子操作可以避免竞态条件和数据不一致。 使用原子操作时,需要根据具体的应用场景选择合适的内存顺序。