跳到主要内容

C 语言原子操作

在多线程编程中,多个线程可能会同时访问和修改共享资源。如果没有适当的同步机制,可能会导致数据竞争(Data Race)和未定义行为。原子操作(Atomic Operations)是一种在多线程环境中确保操作不可分割的机制,能够避免数据竞争问题。

什么是原子操作?

原子操作是指在多线程环境中,一个操作要么完全执行,要么完全不执行,不会被其他线程打断。换句话说,原子操作是不可分割的,它保证了操作的完整性。

在C语言中,原子操作通常用于对共享变量的读写操作,以确保在多线程环境中不会出现数据竞争。

C 语言中的原子操作支持

C11标准引入了对原子操作的支持,定义在 <stdatomic.h> 头文件中。通过使用原子类型和原子操作函数,我们可以安全地在多线程环境中操作共享变量。

原子类型

C11标准定义了一系列原子类型,例如 atomic_intatomic_long 等。这些类型可以用来声明原子变量。

c
#include <stdatomic.h>

atomic_int counter = ATOMIC_VAR_INIT(0);

原子操作函数

C11标准提供了一系列原子操作函数,例如 atomic_loadatomic_storeatomic_fetch_add 等。这些函数用于对原子变量进行操作。

c
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>

atomic_int counter = ATOMIC_VAR_INIT(0);

int increment_counter(void* arg) {
for (int i = 0; i < 100000; i++) {
atomic_fetch_add(&counter, 1);
}
return 0;
}

int main() {
thrd_t thread1, thread2;
thrd_create(&thread1, increment_counter, NULL);
thrd_create(&thread2, increment_counter, NULL);

thrd_join(thread1, NULL);
thrd_join(thread2, NULL);

printf("Counter value: %d\n", atomic_load(&counter));
return 0;
}

代码解释

  • atomic_int counter = ATOMIC_VAR_INIT(0); 声明并初始化一个原子整数变量 counter
  • atomic_fetch_add(&counter, 1); 原子地将 counter 的值加1。
  • atomic_load(&counter); 原子地读取 counter 的值。

输出

plaintext
Counter value: 200000

实际应用场景

原子操作在多线程编程中有广泛的应用,特别是在需要高效同步的场景中。以下是一些常见的应用场景:

  1. 计数器:在多线程环境中,多个线程可能需要同时更新一个计数器。使用原子操作可以确保计数器的值正确更新,而不会出现数据竞争。

  2. 标志位:在多线程环境中,标志位通常用于控制线程的执行流程。使用原子操作可以确保标志位的读写是线程安全的。

  3. 无锁数据结构:原子操作是实现无锁数据结构(如无锁队列、无锁栈等)的基础。无锁数据结构通过原子操作实现线程安全,避免了锁的开销。

总结

原子操作是C语言中用于确保多线程环境中操作安全的重要机制。通过使用C11标准提供的原子类型和原子操作函数,我们可以避免数据竞争和未定义行为,确保程序的正确性和可靠性。

在实际编程中,原子操作广泛应用于计数器、标志位和无锁数据结构等场景。掌握原子操作的使用方法,对于编写高效、可靠的多线程程序至关重要。

附加资源与练习

  • 练习1:修改上面的代码,使用 atomic_fetch_sub 函数实现一个递减计数器。
  • 练习2:尝试实现一个简单的无锁队列,使用原子操作确保线程安全。
提示

深入学习原子操作时,建议阅读C11标准文档中关于 <stdatomic.h> 的详细说明,了解更多原子操作函数的使用方法。