线程安全函数:

当一个函数被多个并发执行的线程同时调用时,均能产生正确的结果。


可重入函数

重入即可重复进入,意味着这个函数可以被中断。

在多线程或异常控制流,当函数运行到中途时,有可能被打断转去执行另一个函数(被暂停的这个函数有可能被再次调用),这种情况下该函数的数据或状态等不会被破坏,行为确定,这种情况下成为该函数可重入。

举例说明:若一个函数(假设为foo()函数)执行过程中收到某一个信号,它会暂停当前正在执行的函数转去执行信号处理函数,而恰巧该信号处理函数中也调用该了foo()函数,这样就发生了所谓的重入。若信号处理函数执行foo()产生了正确的结果,且之前被暂停的foo()函数也正确执行,则该函数为可重入函数。



可重入函数和线程安全函数的关系:

一般来说,可重入函数一定是线程安全的,它是线程安全的一种,但线程安全函数不一定是可重入函数,关系如下图:


     wKiom1crLj_Ay6JCAADvTywx0vY295.png


确保线程安全:

一个进程中的每个线程私有的空间主要包括栈和寄存器,而该进程中的全局和静态变量,堆都是被该进程中所有线程所共享的,因此对所有线程来讲,要访问这些共享变量,需对变量进行加锁,以保证线程安全。


当一个函数中用到了全局变量和静态变量,那么它一定不是可重入的,也不是线程安全的,但是当我们对全局变量和静态变量访问时使用互斥量或信号量加锁后,它是线程安全的,但它仍然不是可重入的,因为互斥锁一般是针对不同线程的访问,同一线程的访问可能出现死锁等问题。


全局变量 --- 生命周期同进程生命周期,可见域是全局
静态变量 --- 生命周期同进程生命周期,可见域是定义变量的域
成员变量 --- 生命周期同类的实例,可见域是类内部(外部只能间接调用,或根本无法调用)
临时变量 --- 生命周期在定义变量的域内,可见域是定义变量的域


确保可重入:

1.不在函数内部使用全局和静态数据;

2.不返回全局和静态数据(某些函数将计算结果放在静态结构中,并返回一个指向这个结构的指针。如果我们从并发线程中调用这些函数,那么将可能发生灾难,因为正在被一个线程使用的结果会被另一个线程悄悄地覆盖了);

3.函数内部不调用不可重入函数;


显式可重入函数:如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的自动栈变量(也就是说没有引用静态或全局变量),那么函数就是显示可重入的,也就是说不管如何调用,我们都可断言它是可重入的。

隐式可重入函数:可重入函数中的一些参数是引用传递(使用了指针),也就是说,在调用线程小心地传递指向非共享数据的指针时,它才是可重入的。


总结:

  1. 判断一个函数是不是线程安全的,在于判断它是否可以在多个并发执行的线程同时运行的时候,输出结果是正确的。

  2. 判断一个函数是否是可重入的,在于判断该函数是否可以被打断,且恢复运行后是否可以输出正确的结果。

  3. 什么时候我们需要可重入函数呢?只有一个函数需要在同一个线程中需要进入两次以上,我们才需要可重入函数。这些情况主要是异步信号处理,递归函数等等。(不可重入函数的递归函数也不一定会出错,取决于你怎么定义和使用该函数)。大部分时候,我们并不需要函数是可重入的。

  4. 常用的malloc函数是一个典型的不可重入函数,但是是线程安全函数,我们可以方便的在多个线程中同时调用malloc,但是,如果将malloc函数放入信号处理函数中去,这是一件很危险的事情。