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

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

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

完整代码

c 复制代码
#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;
}

编译 & 运行命令

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

运行结果示例

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

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

核心代码解释

  1. 原子变量定义

    ini 复制代码
    atomic_int atomic_counter = ATOMIC_VAR_INIT(0);

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

  2. 原子自增操作

    scss 复制代码
    atomic_fetch_add(&atomic_counter, 1);

    atomic_fetch_add 是原子加法函数,作用是把 atomic_counter 的值加 1,整个过程「不可打断」。

    对比普通自增 normal_counter++:它会被拆分为「读取当前值 → 加 1 → 写回」三步,若线程在这三步之间被切换,就会导致数据丢失(比如两个线程同时读到底值 100,都加 1 写回 101,本该加 2 却只加了 1)。

  3. 原子读取操作

    scss 复制代码
    atomic_load(&atomic_counter)

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


总结

  1. 原子操作的核心:把「读-改-写」这类多步操作变成「不可打断的一步操作」,解决多线程并发修改变量的竞态问题。
  2. Linux 应用层使用方式 :基于 C11 的 stdatomic.h 实现(无需依赖内核接口),常用接口包括 atomic_fetch_add(原子加)、atomic_fetch_sub(原子减)、atomic_store(原子写)、atomic_load(原子读)等。
  3. 适用场景:简单的数值型变量并发修改(如计数器、标志位),复杂场景(如结构体修改)仍需用互斥锁(pthread_mutex)。
相关推荐
Sokach101518 小时前
Linux Shell 脚本从零到能用:一个新手的一天学习总结
linux
AlfredZhao1 天前
Docker 容器时区不对,`timedatectl` 不存在怎么办?
linux·timezone
zzzzzz3103 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode3 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
A小辣椒5 天前
TShark:Wireshark CLI 功能
linux
A小辣椒5 天前
TShark:基础知识
linux
AlfredZhao5 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao5 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334666 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪6 天前
linux 拷贝文件或目录到指定的位置
linux