原子操作 (基于Linux 应用层 C 语言)

12 阅读3分钟

原子操作的核心是:一个操作在执行过程中不会被其他线程打断,要么完全执行完,要么完全不执行,能解决多线程并发下的竞态问题(比如多个线程同时修改同一个变量导致数据错乱)。

Linux 应用层常用的原子操作接口是 stdatomic.h (C11 标准)提供的原子类型和操作函数,下面通过「普通变量并发修改」和「原子变量并发修改」的对比 Demo,直观展示原子操作的作用。

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdatomic.h>
#include <unistd.h>

// 1. 普通全局变量(非原子)
int normal_counter = 0;
// 2. 原子全局变量(C11 标准,Linux 应用层推荐)
atomic_int atomic_counter = ATOMIC_VAR_INIT(0);

// 线程函数:对普通变量进行 10000 次自增(无保护)
void *normal_incr(void *arg) {
    for (int i = 0; i < 10000; i++) {
        // 普通自增:拆分为 "读-改-写" 三步,可能被打断
        normal_counter++;
    }
    pthread_exit(NULL);
}

// 线程函数:对原子变量进行 10000 次自增(原子操作)
void *atomic_incr(void *arg) {
    for (int i = 0; i < 10000; i++) {
        // 原子自增:一步完成,不会被打断
        atomic_fetch_add(&atomic_counter, 1);
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t tid1, tid2, tid3, tid4;

    // ========== 测试普通变量(非原子) ==========
    // 创建 2 个线程同时修改普通变量
    pthread_create(&tid1, NULL, normal_incr, NULL);
    pthread_create(&tid2, NULL, normal_incr, NULL);
    // 等待线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    printf("普通变量(非原子)最终值:%d(预期 20000)\n", normal_counter);

    // ========== 测试原子变量 ==========
    // 创建 2 个线程同时修改原子变量
    pthread_create(&tid3, NULL, atomic_incr, NULL);
    pthread_create(&tid4, NULL, atomic_incr, NULL);
    // 等待线程结束
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    printf("原子变量最终值:%d(预期 20000)\n", atomic_load(&atomic_counter));

    return 0;
}

编译 & 运行命令

# 编译(需要链接 pthread 库)
gcc atomic_demo.c -o atomic_demo -lpthread -std=c11
# 运行
./atomic_demo

运行结果示例

普通变量(非原子)最终值:18765(预期 20000)
原子变量最终值:20000(预期 20000)

(注:普通变量的结果每次运行都可能不同,且小于 20000;原子变量的结果永远是 20000)

核心代码解释

  1. 原子变量定义

    atomic_int atomic_counter = ATOMIC_VAR_INIT(0);
    

    atomic_int 是 C11 定义的原子整型,ATOMIC_VAR_INIT(0) 是原子变量的初始化宏,确保初始化过程也是原子的。

  2. 原子自增操作

    atomic_fetch_add(&atomic_counter, 1);
    

    atomic_fetch_add 是原子加法函数,作用是把 atomic_counter 的值加 1,整个过程「不可打断」。
    对比普通自增 normal_counter++:它会被拆分为「读取当前值 → 加 1 → 写回」三步,若线程在这三步之间被切换,就会导致数据丢失(比如两个线程同时读到底值 100,都加 1 写回 101,本该加 2 却只加了 1)。

  3. 原子读取操作

    atomic_load(&atomic_counter)
    

    读取原子变量的值也需要用原子接口,确保读取到的是「最新的、完整的」值。


总结

  1. 原子操作的核心:把「读-改-写」这类多步操作变成「不可打断的一步操作」,解决多线程并发修改变量的竞态问题。
  2. Linux 应用层使用方式:基于 C11 的 stdatomic.h 实现(无需依赖内核接口),常用接口包括 atomic_fetch_add(原子加)、atomic_fetch_sub(原子减)、atomic_store(原子写)、atomic_load(原子读)等。
  3. 适用场景:简单的数值型变量并发修改(如计数器、标志位),复杂场景(如结构体修改)仍需用互斥锁(pthread_mutex)。