可重入函数与线程安全函数详解

进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用exit或longjmp),则继续执行在捕捉到信号时进程正在执行的正常指令序列。

可重入函数

在信号处理程序中,不能判断捕捉到信号时进程执行到何处。如果进程正在执行malloc,在其堆中分配另外的存储空间,或者调用free释放已分配的空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用malloc或者free。此时很可能会对进程造成破坏,因为malloc通常为它所分配的存储区维护一个链表,而插入执行信号处理程序时,进程可能正在更改此链表。又或者进程正要输出一个全局变量,此时捕捉到信号执行信号处理程序,其可能会更改该全局变量,从信号处理程序返回后继续执行输出指令,此时原来内容被覆盖,无疑引发了错误。

Single UNIX Specification说明了在信号处理程序中保证调用安全的函数。这些函数是可重入的并被称为是异步信号安全的。除了可重入以外,在信号处理操作期间,它会阻塞任何会引起不一致的信号的发送。

下图为异步信号安全函数,没有列入其中的大多数函数都是不可重入的。

图中没有包括longjmp和siglongjmp。这是因为主例程以非可重入方式正在更新一个数据结构时可能产生信号。如果不是从信号处理程序返回而是调用siglongjmp,那么该数据结构可能是部分更新的。如果应用程序将要做更新全局数据结构这样的事情,而同时要捕捉某些信号,而这些信号的处理程序又会引起执行siglongjmp,则在更新这种数据结构时要阻塞此类信号。

函数之所以不可重入,有以下原因:

  1. 它们使用静态(全局)数据结构。
  2. 它们调用malloc或者free。
  3. 它们是标准I/O函数。标准I/O库的很多实现都以不可重入方式使用全局数据结构。

比如:

1
2
3
4
5
6
7
8
9
10
int g_var = 1;
int f()
{
g_var = g_var + 2;
return g_var;
}
int g()
{
return f() + 2;
}

f()使用了全局变量,所以是不可重入的,最后的结果与信号处理程序的操作有关;g()调用了f(),所以g也是不可重入的。如果做以下更改,则f和g都可重入:

1
2
3
4
5
6
7
8
9
int f(int i)
{
return i + 2;
}

int g(int i)
{
return f(i) + 2;
}

应当了解,即使信号处理程序调用的是图中的可重入函数,但是由于每一个线程只有一个errno变量,所以信号处理程序可能会修改原先值。因此,作为一种通用的规则,当信号处理程序调用图中的函数时,应当在调用前保存errno,在调用后恢复errno。
如果在信号处理程序中调用一个非可重入的函数,则其结果是不可预知的。调用可重入的函数,需要提前保存errno变量的值,执行完成后恢复该值。
总结的说:可重入函数都有一个特性:当它们被多个线程调用时,不会引用任何共享数据。它们只访问自己的局部变量或参数。

线程安全函数

所有的函数集合可以被划分为不想交的线程安全函数和线程不安全函数集合。可重入函数是线程安全函数的一个真子集

有时候会自动的把可重入函数与线程安全函数画等号,其实它们之间还是有些区别的。可重入函数通常要比不可重入的线程安全函数高效一些,因为它们不需要同步操作。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。一般来说,一个函数被称为线程安全的,当且仅当多个线程对同一函数并发反复的进行调用时,一直产生正确结果。
可以定义四个线程不安全函数类:

  1. 不保护共享变量的函数。
  2. 保持跨越多个调用的状态的函数。
  3. 返回指向静态变量的指针的函数。
  4. 调用线程不安全的函数。函数g是线程不安全的,f调用g,如果g属于第二类线程不安全函数,则f也是线程不安全的;但是如果g属于第一或第三类函数,如果在f中再使用互斥保护锁,则f还是线程安全的。
    比如线程安全中最典型的一个例子:

    运行结果如下:

    发生这种情况是因为,在修改一个值的时候,其具体步骤是:从内存中将数据加载到寄存器,寄存器修改数据,然后将修改后的数据保存到内存原位置。当线程1加载到寄存器后修改,没有重新放回内存时被线程2打断,线程2加载到的数据就是线程1更新前的数据,所以会产生上图结果。
    如果改成可重入的,那么一定是线程安全的了

    运行结果如下:

参考资料:
《UNIX环境高级变量 3th》(APUE)
《linux/UNIX系统编程手册》(tlpi)
《深入理解计算机系统 3th》(CSAPP)