[学习] C语言中的原子操作(示例)

发布于:2025-07-01 ⋅ 阅读:(26) ⋅ 点赞:(0)

🧠C语言中的原子操作

C语言编程中线程安全的实现方法(示例)一文中,介绍了多种线程安全的做法,其中之一就是燕子操作。今天再对这种高效的线程安全操作方法进行更详细的介绍。

在并发编程中,原子操作是保证线程安全、提高程序性能的重要手段。本文将详细介绍 C 语言中原子操作的实现原理、使用方法,并提供可以编译执行的完整代码示例。


🔍一、什么是原子操作?

原子操作(Atomic Operation)是指不会被线程调度机制打断的操作。即该操作要么全部完成,要么完全不做,不存在中间状态。这种特性使得它非常适合用于多线程环境下的共享资源访问控制。

原子操作比锁(如互斥量 mutex)更轻量,开销更低,适用于一些简单的同步场景,比如计数器、标志位设置等。


💡二、为什么需要原子操作?

在多线程环境下,如果不加保护地对共享变量进行读写操作,可能会导致数据竞争(Data Race),从而引发不可预测的结果。

例如:

int counter = 0;

void* increment(void* arg) {
    for (int i = 0; i < 1000000; ++i)
        counter++;
    return NULL;
}

如果两个线程同时运行这段代码,最终 counter 的值可能小于预期(2,000,000)。这是因为 counter++ 并不是原子操作,它包括读取、递增、写回三个步骤,在并发时可能发生冲突。


⚙️三、C语言中原子操作的实现方式

✅ 1. 使用 GCC 内建函数(__sync 系列)

GCC 提供了一组以 __sync_ 开头的原子操作函数,它们是平台相关的,但兼容性较好,常用于旧版代码或嵌入式系统。

常用函数:
函数名 功能说明
__sync_fetch_and_add() 加法并返回原值
__sync_fetch_and_sub() 减法并返回原值
__sync_fetch_and_or() 按位或并返回原值
__sync_fetch_and_and() 按位与并返回原值
__sync_bool_compare_and_swap() CAS 操作(比较并交换)
示例:使用 __sync_fetch_and_add
#include <stdio.h>
#include <pthread.h>

int counter = 0;

void* increment(void* arg) {
    for (int i = 0; i < 1000000; ++i)
        __sync_fetch_and_add(&counter, 1);
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("Final counter value: %d\n", counter); // 应为 2000000
    return 0;
}

✅ 编译命令:

gcc -o atomic_example atomic_example.c -lpthread

✅ 2. 使用 C11 标准 <stdatomic.h>

C11 引入了 _Atomic 类型限定符和 <stdatomic.h> 头文件,提供了跨平台的原子操作接口。

支持的类型:
  • atomic_int
  • atomic_long
  • atomic_flag
  • 更复杂的结构体也可以通过 atomic_store, atomic_load, atomic_exchange, atomic_compare_exchange_strong/weak 等函数来操作。
示例:使用 atomic_int
#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>

atomic_int counter = 0;

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

int main() {
    pthread_t t1, t2;

    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("Final counter value: %d\n", atomic_load(&counter)); // 应为 2000000
    return 0;
}

✅ 编译命令(启用 C11):

gcc -std=c11 -o atomic_c11 atomic_c11.c -lpthread

✅ 3. 使用 atomic_flag 实现自旋锁(Spinlock)

atomic_flag 是唯一一个保证无锁(lock-free)的原子类型,适合实现自旋锁。

示例:实现一个简单的自旋锁
#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>

atomic_flag lock = ATOMIC_FLAG_INIT;

void spin_lock() {
    while (atomic_flag_test_and_set(&lock)) {
        // 等待直到锁释放
    }
}

void spin_unlock() {
    atomic_flag_clear(&lock);
}

int counter = 0;

void* increment(void* arg) {
    for (int i = 0; i < 1000000; ++i) {
        spin_lock();
        counter++;
        spin_unlock();
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("Final counter value: %d\n", counter); // 应为 2000000
    return 0;
}

✅ 编译命令:

gcc -std=c11 -o spinlock_example spinlock_example.c -lpthread

⚖️四、原子操作 vs 锁机制对比

特性 原子操作 锁机制(如 mutex)
性能 更快 相对较慢
使用复杂度 简单 较复杂
阻塞行为 不阻塞 可能阻塞当前线程
死锁风险
可扩展性 适合简单操作 适合复杂逻辑

📌五、注意事项

  • 不要过度依赖原子操作:虽然高效,但只能处理简单的操作,不能替代互斥锁。
  • 内存顺序(Memory Order):C11 支持不同内存顺序(如 memory_order_relaxed, memory_order_seq_cst),需谨慎选择以避免错误。
  • 平台差异:某些原子操作在不同平台上表现不一致,推荐使用标准库函数确保移植性。

🧪六、结语

原子操作是现代并发编程中不可或缺的工具之一。它不仅提高了程序效率,也减少了因锁带来的复杂性和潜在死锁问题。掌握其原理和使用方法,对于编写高性能、线程安全的 C 程序至关重要。

如果你正在开发多线程应用,不妨尝试用原子操作优化你的代码吧!


研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)



网站公告

今日签到

点亮在社区的每一天
去签到