首页 > 代码库 > 函数的可重入性、线程安全函数、异步信号安全函数

函数的可重入性、线程安全函数、异步信号安全函数

  重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。,常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。

  要确保函数可重入,需满足一下几个条件:

    1、不在函数内部使用静态或全局数据 
    2、不返回静态或全局数据,所有数据都由函数的调用者提供。 
    3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
    4、不调用不可重入函数。

 

  线程安全:一个函数被称为线程安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。要确保函数线程安全,主要需要考虑的是线程之间的共享变量。在对这些共享变量进行访问时,如果要保证线程安全,则必须通过加锁的方式。  

  可重入与线程安全并不等同,一般说来,可重入的函数一定是线程安全的,但反过来不一定成立。

   

  可以被信号控制器安全调用的函数被称为"异步信号安全"函数。信号就像硬件中断一样,会打断正在执行的指令序列。信号处理函数无法判断捕获到信号的时候,进程在何处运行。如果信号处理函数中的操作与打断的函数的操作相同,而且这个操作中有静态数据结构等,当信号处理函数返回的时候(当然这里讨论的是信号处理函数可以返回),恢复原先的执行序列,可能会导致信号处理函数中的操作覆盖了之前正常操作中的数据。

  不可重入函数的原因在于:

    1. 已知它们使用静态数据结构

    2.它们调用malloc和free.因为malloc通常会为所分配的存储区维护一个链接表,而插入执行信号处理函数的时候,进程可能正在修改此链接表。

    3. 它们是标准IO函数.因为标准IO库的很多实现都使用了全局数据结构。

  即使对于可重入函数,在信号处理函数中使用也需要注意一个问题就是errno。一个线程中只有一个errno变量,信号处理函数中使用的可重入函数也有可能会修改errno。例如,read函数是可重入的,但是它也有可能会修改errno。因此,正确的做法是在信号处理函数开始,先保存errno;在信号处理函数退出的时候,再恢复errno。

  举个例子

  例如,程序正在调用printf输出,但是在调用printf时,出现了信号,对应的信号处理函数也有printf语句,就会导致两个printf的输出混杂在一起。如果是给printf加锁的话,同样是上面的情况就会导致死锁。对于这种情况,采用的方法一般是在特定的区域屏蔽一定的信号。

  屏蔽信号的方法:

    1. signal(SIGPIPE, SIG_IGN); //忽略一些信号

    2. sigprocmask()  sigprocmask只为单线程定义的

    3 pthread_sigmask() pthread_sigmasks可以在多线程中使用

 

  所以异步信号安全函数 也尅就是函数中没有访问到被某个同步锁锁住的变量,即使函数执行的过程中被某个信号中断,程序也不会发生死锁的情况。

 

总结
判断一个函数是不是可重入函数,在于判断其能否可以被打断,打断后恢复运行能够得到正确的结果。(打断执行的指令序列并不改变函数的数据)
判断一个函数是不是线程安全的,在于判断其能否在多个线程同时执行其指令序列的时候,保证每个线程都能够得到正确的结果。
如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。
如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是"异步-信号安全"的。