求职不利,趁十一假期复习APUE v2线程和信号章节,两天时间看完。书中10.6和12.5两节分别是信号和线程的重入介绍。但是未对异步信号安全、线程安全、可重入概念做统一对比,难以彻悟。针对于此,写下本文。
1. 三个概念,线程安全,可重入,信号安全
先简单提一下,
线程安全,主要是针对数据竞争来说的,就是说:如果数据不需要共享,那就让每个线程私有;如果需要共享,那就加锁。
信号安全,其实也就是异步信号安全,是说线程在信号处理函数当中,不管以任何方式调用你的这个函数如果不死锁不修改数据,那就是信号安全的。也就是说一个可重入函数在信号处理函数当中不影响调用他的人本身的状态,其实就是一个task_struct有很多指针指向它,你通过多线程调一个可重入函数,函数有自己所在的task的代码段和数据段,如果你在信号处理里面调它,实际上是换了一个指向同一个task_struct里面内容的指针来操作,会有问题。这种情形通过加锁是解决不了问题的,比如你正在调用printf过程中,遇到一个信号,转而去处理这个信号,并且在处理这个信号的信号处理函数当中正巧要调用printf,那么屏幕上就是乱的,加锁行不行呢?那就很好玩了。所以printf不是异步信号安全的。
可重入性:就是无论以什么方式多次调用都不会出现问题,不会出现对可能有修改的静态数据的访问,不会出现对全局变量(比如errno)的访问。严格讲可重入要区分线程安全(弱可重入)还是信号安全(强可重入)两点,但是一般说可重入就是指信号安全。由于信号安全要求高于线程安全,所以说如果一个函数是可重入的,那一定是线程安全的(反之不一定)。
2. 下面详细解释三者
2.1 可重入性(reentrant)针对函数,它有两个方面的内涵:
1)可并行/并发,同时进入:指可重入函数被某任务调用时,其它任务可同时进行调用而不产生错误的结果;或称在相同的输入情况下可重入函数的执行所产生的效果,并不因其并发的调用而产生不同,也称并发安全。
2)中断后可重新进入:指可重入函数可被任意的中断,当中断执行完毕,返回断点可以继续正确的运行下去;或称在相同的输入情况下可重入函数的执行所产生的结果,并不因为在函数执行期间有中断的调用而产生不同,也称中断安全。
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反, 不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥 (或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断, 稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时 保护自己的数据。
可重入函数一般要满足:
- 不为连续的调用持有静态数据。
- 不返回指向静态数据的指针;所有数据都由函数的调用者提供。
- 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
- 绝不调用任何不可重入函数。
出于以下任意某个原因,其余函数是不可重入的:
- 它们调用了
malloc
或free:malloc/free 是在 C 标准中规定的,而在 C 中是没有进程/线程的概念。
- 众所周知它们使用了静态数据结构体。
- 它们是标准 I/O 程序库的一部分:glibc的c库算是线程安全了,却不保证可重入或异步信号安全。
2.2 线程安全(MT-safe)不仅仅针对函数,它主要是指数据或程序的安全这一结果,所以有不同的层次上讲的线程安全:如线程安全的函数,线程安全的库 。本文还会引入线程安全的类这一概念。通常意义上一个线程安全的函数是指有可重入函数第一个内涵的函数即并发安全的函数。但需要注意的是即使是一个从函数级别上并不安全的函数,如果使其不安全的因素在特定应用中并不存在时,这个函数对于该应用来讲同样也是线程安全的。例如对于全局变量的访问,一般而言未命名同步方式访问肯定是非线程安全的,但如果所有可能同时发生的访问均是只读访问则从结果上讲也是线程安全的。
不要混淆可重入与线程安全。在程序员看来,这是两个独立的概念:函数可以是可重入的,是线程安全的,或者 二者皆是,或者二者皆非。不可重入的函数不能由多个线程使用。另外,或许不可能让某个 不可重入的函数是线程安全的。关于标准IO库是不是线程安全的要依据实际情况而定,不同的库有不同实现,一般为了方便用户使用,在支持多线程的系统上都配备了线程安全的库,但却不是异步安全的,因为,信号一定是异步的,不是线程的加锁机制(本质是同步化)就可以解决的。
2.3 信号安全,信号的本质软中断,中断在本质上是异步的,所谓异步信号安全同线程安全一样,也是站在结果上考虑的,指在信号的中断处理过程中的安全。通常意义上一个异步信号安全的函数是指可以在异步信息处理函数中调用而不会出现异常的函数。同样需要注意到即使一个从函数级别上并非异步信息安全的函数,如果在信息处理函数中调用,也并不一定会产生不安全的结果。
3. 三个概念的异同
从函数级别考虑,仅从概念上就可以发现可重入函数一定是线程安全函数,也是异步信号安全函数;多线程安全函数却要弱得多,并非一定要是可重入函数,它只要求并发无误即可;虽然异步信号函数与可重入函数的描述方式有所不同,但两者从实现层面上讲是完全一致的。
示例:比如:strtok函数是既不可重入的,也不是线程安全的;加锁的strtok不是可重入的(并发安全但信号不安全,涉及内部静态变量,见参考文献2),但线程安全(并发安全);而strtok_r既是可重入的,也是线程安全的。
参考文献:
2 strtok和strtok_r 实现分析 http://blog.csdn.net/liuintermilan/article/details/6283705