在前面的学习中,我们已经介绍过了可重入函数,下面我们先来复习一些基本的概念:
- 重入:即重复进入。同一个函数被不同的执行流调用,有可能出现第一次调用还未返回,就再次进入该函数开始了下一次的调用。
- 可重入:函数可由多个线程并发使用,而不必担心数据错误。可重入函数可以在任意时刻被中断,稍后再继续运行时并不会丢失数据。可重入性解决函数运行结果的确定性和可重复性。
- 可重入函数:如果一个函数只访问自己的局部变量或参数,则称为可重入函数。
- 不可重入:当程序被多个线程反复调用,产生的结果出现错误。
- 不可重入函数:当函数访问一个全局的变量或者参数时,有可能因为重入而造成混乱,这样的函数称为不可重入函数。
- 可重入函数编写规范:
(1)不在函数内部使用静态或全局数据;
(2)不返回静态或全局数据,所有数据都由函数的调用者提供;
(3)使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据;
(4)如果必须访问全局变量,利用互斥机制来保护全局变量;
(5)不调用不可重入函数。
- 如果一个函数符合以下条件之一,则是不可重入的:
(1)函数中使用了静态变量,无论是全局静态变量还是局部静态变量;
(2)函数返回静态变量;
(3)函数中调用了不可重入函数;
(4)函数体内使用了静态的数据结构;
(5)函数体内调用了malloc()或者free()函数;
(6)函数体内调用了其他标准I/O函数;
总的来说,如果一个函数在重入条件下使用了未受保护的共享资源,那么它是不可重入的。
- 线程安全:如果一个函数在同一时刻可以被多个线程安全地调用,则称该函数是线程安全的。
- 线程安全函数解决多个线程调用函数时访问共享资源的冲突问题。
- 可重入与线程安全的关系:
(1)如果一个函数当中有全局变量,则该函数既不是线程安全的也不是可重入的;
(2)线程安全是在多线程情况下引发的,而可重入函数可以在只有一个线程的情况下发生;
(3)线程安全不一定是可重入的,但可重入函数一定是线程安全的;
(4)如果对临界资源的访问加锁,则这个函数是线程安全的;但如果重入函数加锁还未释放,则会产生死锁,因此不能重入;
(5)线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作不影响结果,使结果是相同的。
- 为了写一个稳定的多线程程序,必须遵守线程安全,但不一定遵守可重入。
- 可重入函数是线程安全函数的一个真子集。也就是说,如果函数是可重入的,就可以保证它是线程安全的。
示例:
在多线程条件下,函数应当是线程安全的,进一步更强的条件是可重入的。可重入函数保证了在多线程条件下,函数的状态不会出现错误。以下分别是一个不可重入和可重入函数的示例:
static int tmp;
void func1(int *x,int *y){
tmp = *x;
*x = *y;
*y = tmp;
}
void func2(int *x,int *y){
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
func1是不可重入的,func2是可重入的。因为在多线程条件下,操作系统会在func1还没有执行完的情况下,切换到另一个线程中,那个线程可能再次调用func1,这样状态就错了。
代码实例:
#include <stdio.h>
#include<signal.h>
int g_val = 0;
void fun(){
int i = 3;
while(i--){
g_val++;
printf("g_val = %d\n",g_val);
sleep(1);
}
}
int main(){
signal(2,fun);//2号信号:ctrl+c。收到2号信号,执行自定义处理动作
fun();
printf("main : g_val = %d\n",g_val);
}
结果演示:
本代码原本想实现最终g_val为3,结果在为2时,我们按下“Ctrl+c”,产生了错误。由于变量是全局的,收到2号信号后,继续进行+1运算,最终执行结果为6。因此该程序中,fun函数是不可重入函数。
为了防止这种情况的发生,我们可以将g_val改为局部变量。这样函数被二次调用时,两次结果最终并不影响并且相等。