什么是原子性和非原子性?
v1.0 2024年5月28日 发布于博客园
在嵌入式Linux C编程中,原子性(Atomicity)和非原子性(Non-Atomicity)是非常重要的概念,尤其是在处理多线程或多进程环境时。以下是对这两个概念的详细解释:
原子性(Atomicity)
原子性是指一个操作在执行过程中不可分割,要么完全执行,要么完全不执行。在多线程环境中,原子性操作不会被其他线程的操作打断。
例子:
-
简单的原子操作:
- 一些基本的读写操作在某些架构上是原子的。例如,在许多32位系统上,对32位整数的读写是原子的。
int counter = 0; counter = 1; // 这是一个原子操作(在许多架构上)
-
原子操作函数:
- 在嵌入式Linux中,可以使用GCC提供的内置原子操作函数,如
__sync_fetch_and_add
、__sync_lock_test_and_set
等。
#include <stdio.h> int main() { int counter = 0; __sync_fetch_and_add(&counter, 1); // 原子性加1操作 printf("Counter: %d\n", counter); return 0; }
- 在嵌入式Linux中,可以使用GCC提供的内置原子操作函数,如
-
原子操作库:
- 使用Linux内核提供的原子操作库(atomic operations library),如
atomic_t
类型和相关的操作函数。
#include <linux/atomic.h> atomic_t counter = ATOMIC_INIT(0); void increment_counter() { atomic_inc(&counter); // 原子性加1操作 }
- 使用Linux内核提供的原子操作库(atomic operations library),如
非原子性(Non-Atomicity)
非原子性是指一个操作在执行过程中可能会被其他线程的操作打断。这种操作在多线程环境中可能导致竞争条件(Race Condition),从而引发数据不一致或其他并发问题。
例子:
-
非原子的复合操作:
- 例如,
i++
在许多架构上不是原子的,因为它实际上由多个步骤组成(读取、修改和写入)。
int counter = 0; counter++; // 这是一个非原子操作
- 例如,
-
竞争条件:
- 当多个线程同时执行非原子操作时,可能会导致不可预测的结果。
#include <pthread.h> #include <stdio.h> int counter = 0; void* increment(void* arg) { for (int i = 0; i < 10000; i++) { counter++; // 非原子操作,可能导致竞争条件 } 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("Counter: %d\n", counter); // 结果可能不正确 return 0; }
确保原子性的方法
-
使用原子操作函数:
- 使用GCC内置的原子操作函数,如
__sync_fetch_and_add
、__sync_lock_test_and_set
等。
- 使用GCC内置的原子操作函数,如
-
使用原子操作库:
- 使用Linux内核提供的原子操作库(atomic operations library)。
-
使用互斥锁(Mutex):
- 使用互斥锁来保护非原子操作,确保在多线程环境中操作的原子性。
#include <pthread.h> #include <stdio.h> int counter = 0; pthread_mutex_t lock; void* increment(void* arg) { for (int i = 0; i < 10000; i++) { pthread_mutex_lock(&lock); counter++; // 受互斥锁保护,确保原子性 pthread_mutex_unlock(&lock); } return NULL; } int main() { pthread_t t1, t2; pthread_mutex_init(&lock, NULL); pthread_create(&t1, NULL, increment, NULL); pthread_create(&t2, NULL, increment, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_mutex_destroy(&lock); printf("Counter: %d\n", counter); // 结果正确 return 0; }
总结
- 原子性:操作不可分割,要么全部执行,要么全部不执行,确保数据一致性和线程安全。
- 非原子性:操作可能被打断,可能导致数据不一致,需要额外的同步机制来确保线程安全。
在嵌入式Linux C编程中,理解和正确使用原子性和非原子性对于编写安全有效的并发程序至关重要。
原子上下文 和 中断上下文
在嵌入式Linux C编程中,“原子上下文”和“中断上下文”是两个重要的概念,特别是在处理并发和硬件中断时。理解这些概念对于编写高效和稳定的嵌入式系统代码至关重要。
原子上下文(Atomic Context)
原子上下文是指在代码执行过程中不允许被中断的环境或代码段。在这个上下文中,代码执行是不可分割的,确保了操作的原子性。通常,原子上下文用于保护共享资源,防止竞态条件(Race Condition)。
实现方法:
-
禁用中断:在某些情况下,可以通过禁用中断来确保代码段的原子性。这在嵌入式系统中非常常见,但需要谨慎使用,因为禁用中断会影响系统的实时性。
unsigned long flags; local_irq_save(flags); // 禁用本地中断,保存当前中断状态 // 关键代码段 counter++; local_irq_restore(flags); // 恢复中断状态
-
使用自旋锁(Spinlock):在多处理器系统中,自旋锁是一种常用的同步机制,用于保护共享资源。
spinlock_t lock; spin_lock(&lock); // 获取自旋锁 // 关键代码段 counter++; spin_unlock(&lock); // 释放自旋锁
中断上下文(Interrupt Context)
中断上下文是指在处理中断请求(Interrupt Request, IRQ)时的执行环境。在这个上下文中,代码是由硬件中断触发的,而不是由正常的进程调度触发的。
特点:
- 无进程上下文:在中断上下文中,没有与特定进程相关的上下文。因此,不能进行可能会导致阻塞的操作,如睡眠、等待锁等。
- 快速执行:中断处理程序(Interrupt Handler)应尽可能快地执行,以减少对系统响应时间的影响。
- 不能使用某些内核函数:例如,不能使用可能会睡眠的函数(如
kmalloc
带有GFP_KERNEL
标志的版本)。
示例:
#include <linux/interrupt.h>
#include <linux/gpio.h>
static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
// 处理中断
// 这里的代码在中断上下文中执行
return IRQ_HANDLED; // 表示中断已被处理
}
static int __init my_module_init(void) {
int irq_num = gpio_to_irq(MY_GPIO_PIN);
// 注册中断处理程序
request_irq(irq_num, my_interrupt_handler, IRQF_TRIGGER_RISING, "my_interrupt", NULL);
return 0;
}
static void __exit my_module_exit(void) {
int irq_num = gpio_to_irq(MY_GPIO_PIN);
// 释放中断
free_irq(irq_num, NULL);
}
module_init(my_module_init);
module_exit(my_module_exit);
对比和总结
- 原子上下文:用于确保代码段的原子性,防止竞态条件。可以通过禁用中断或使用自旋锁等机制实现。
- 中断上下文:在处理中断请求时的执行环境,没有进程上下文,不能进行阻塞操作,要求快速执行。
理解和正确使用原子上下文和中断上下文对于编写高效和稳定的嵌入式Linux系统代码至关重要。确保在适当的场景下使用正确的同步和中断处理机制,可以有效地提高系统的可靠性和响应速度。
本文来自博客园,作者:舟清颺,转载请注明原文链接:https://www.cnblogs.com/zqingyang/p/18217958