0%

可重入

概念

可重入函数:如果一个函数在执行过程中被中断服务程序打断,执行中断服务程序之后恢复执行,还能不妨碍之前的执行,就称该函数是可重入的

可重入函数一般用于硬件中断处理递归等应用程序中。

可重入程序/可重入子例程:在多个处理器上能被安全地多次并发调用。

与线程安全的区别:可重入函数的概念在多任务操作系统出现之前就存在了,所以该概念仅仅针对的是单线程执行过程。
一个函数可以是线程安全但非可重入的,例如,该函数每次都使用互斥量来包裹。但是,如果该函数用于中断服务程序,那么,它可能在等待第一次执行过程释放互斥量时陷入饥饿。TODO:陷入饥饿为什么就不是可重入了?

要实现可重入性,函数通常需要满足以下条件:

  • 不使用静态或全局变量:这些变量在多次调用之间共享,可能导致数据竞争。
  • 不依赖于不可重入的函数:例如,标准库中的某些函数可能不是可重入的。
  • 不使用动态内存分配:动态内存分配可能会导致竞争条件。
  • 不使用信号处理:信号处理可能会中断函数的执行。

可重入性在多线程编程和中断处理程序中尤为重要,因为它确保了函数在并发环境下的安全性。

可重入 VS 线程安全

可重入性和线程安全虽然都涉及到并发编程,但它们有不同的侧重点:

可重入性:

定义:一个函数可以在被中断后安全地再次调用,而不会影响其执行结果。
条件:不使用静态或全局变量、不依赖不可重入的函数、不使用动态内存分配、不使用信号处理。
应用场景:主要用于中断处理程序和嵌入式系统。
线程安全:

定义:一个函数或代码块在多线程环境下可以安全地并发执行,而不会导致竞争条件或数据不一致。
条件:通常需要使用同步机制(如互斥锁、信号量)来保护共享资源。
应用场景:主要用于多线程编程。
总结来说,可重入性关注的是函数在被中断后能否安全地再次调用,而线程安全关注的是在多线程环境下能否安全地并发执行。可重入函数不一定是线程安全的,线程安全的函数也不一定是可重入的。

  1. 可重入函数不一定是线程安全的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int counter = 0;

void reentrantFunction() {
int localCounter = counter;
localCounter++;
counter = localCounter;
}

int main() {
reentrantFunction();
printf("Counter: %d\n", counter);
return 0;
}

解释:

reentrantFunction 是可重入的,因为它满足以下条件:

不使用静态或全局变量:虽然 counter 是一个全局变量,但 reentrantFunction 中的操作都是基于局部变量 localCounter,并且没有依赖于函数外部的状态。
不依赖不可重入的函数:reentrantFunction 中没有调用任何不可重入的函数。
不使用动态内存分配:函数中没有使用 malloc 或其他动态内存分配函数。
不使用信号处理:函数中没有涉及信号处理。
因此,reentrantFunction 可以在被中断后安全地再次调用,而不会影响其执行结果。

然而,正因为它使用了全局变量 counter,在多线程环境下可能会导致竞争条件,所以它不是线程安全的。

  1. 线程安全的函数不一定是可重入的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;

void* threadSafeFunction(void* arg) {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
return NULL;
}

int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, threadSafeFunction, NULL);
pthread_create(&thread2, NULL, threadSafeFunction, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Counter: %d\n", counter);
return 0;
}

解释:

这个函数 threadSafeFunction 是线程安全的,因为它使用了互斥锁来保护对 counter 的访问。
然而,它不是可重入的,因为它使用了互斥锁,互斥锁在中断处理程序中可能会导致死锁:

互斥锁的使用:互斥锁用于确保线程安全,但它们在中断处理程序中可能会导致死锁。如果一个线程在持有锁时被中断,然后中断处理程序尝试再次调用该函数并试图获取同一个锁,就会发生死锁。

依赖于锁的状态:函数的执行依赖于锁的状态。如果锁已经被其他线程持有,函数就无法继续执行,必须等待锁释放。这种依赖性使得函数在中断后无法安全地再次调用。

不可重入的行为:由于锁的存在,函数在中断后重新进入时可能无法正确处理锁的状态,从而导致不可预期的行为。

Reference