🧠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 程序至关重要。
如果你正在开发多线程应用,不妨尝试用原子操作优化你的代码吧!
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)