概念
可重入函数:如果一个函数在执行过程中被中断服务程序打断,执行中断服务程序之后恢复执行,还能不妨碍之前的执行,就称该函数是可重入的。
可重入函数一般用于硬件中断处理或递归等应用程序中。
可重入程序/可重入子例程:在多个处理器上能被安全地多次并发调用。
与线程安全的区别:可重入函数的概念在多任务操作系统出现之前就存在了,所以该概念仅仅针对的是单线程执行过程。
一个函数可以是线程安全但非可重入的,例如,该函数每次都使用互斥量来包裹。但是,如果该函数用于中断服务程序,那么,它可能在等待第一次执行过程释放互斥量时陷入饥饿。TODO:陷入饥饿为什么就不是可重入了?
要实现可重入性,函数通常需要满足以下条件:
- 不使用静态或全局变量:这些变量在多次调用之间共享,可能导致数据竞争。
- 不依赖于不可重入的函数:例如,标准库中的某些函数可能不是可重入的。
- 不使用动态内存分配:动态内存分配可能会导致竞争条件。
- 不使用信号处理:信号处理可能会中断函数的执行。
可重入性在多线程编程和中断处理程序中尤为重要,因为它确保了函数在并发环境下的安全性。
可重入 VS 线程安全
可重入性和线程安全虽然都涉及到并发编程,但它们有不同的侧重点:
可重入性:
定义:一个函数可以在被中断后安全地再次调用,而不会影响其执行结果。
条件:不使用静态或全局变量、不依赖不可重入的函数、不使用动态内存分配、不使用信号处理。
应用场景:主要用于中断处理程序和嵌入式系统。
线程安全:
定义:一个函数或代码块在多线程环境下可以安全地并发执行,而不会导致竞争条件或数据不一致。
条件:通常需要使用同步机制(如互斥锁、信号量)来保护共享资源。
应用场景:主要用于多线程编程。
总结来说,可重入性关注的是函数在被中断后能否安全地再次调用,而线程安全关注的是在多线程环境下能否安全地并发执行。可重入函数不一定是线程安全的,线程安全的函数也不一定是可重入的。
- 可重入函数不一定是线程安全的
1 |
|
解释:
reentrantFunction 是可重入的,因为它满足以下条件:
不使用静态或全局变量:虽然 counter 是一个全局变量,但 reentrantFunction 中的操作都是基于局部变量 localCounter,并且没有依赖于函数外部的状态。
不依赖不可重入的函数:reentrantFunction 中没有调用任何不可重入的函数。
不使用动态内存分配:函数中没有使用 malloc 或其他动态内存分配函数。
不使用信号处理:函数中没有涉及信号处理。
因此,reentrantFunction 可以在被中断后安全地再次调用,而不会影响其执行结果。
然而,正因为它使用了全局变量 counter,在多线程环境下可能会导致竞争条件,所以它不是线程安全的。
- 线程安全的函数不一定是可重入的
1 |
|
解释:
这个函数 threadSafeFunction 是线程安全的,因为它使用了互斥锁来保护对 counter 的访问。
然而,它不是可重入的,因为它使用了互斥锁,互斥锁在中断处理程序中可能会导致死锁:
互斥锁的使用:互斥锁用于确保线程安全,但它们在中断处理程序中可能会导致死锁。如果一个线程在持有锁时被中断,然后中断处理程序尝试再次调用该函数并试图获取同一个锁,就会发生死锁。
依赖于锁的状态:函数的执行依赖于锁的状态。如果锁已经被其他线程持有,函数就无法继续执行,必须等待锁释放。这种依赖性使得函数在中断后无法安全地再次调用。
不可重入的行为:由于锁的存在,函数在中断后重新进入时可能无法正确处理锁的状态,从而导致不可预期的行为。