一个可重入的函数简单来说就是鈳以被中断的函数也就是说,可以在这个函数执行的任何时刻中断它转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错誤
《多线程编程指南》中定义,可以被信号控制器安全调用的函数被称为"异步信号安全"函数
因此,我认为可重入与异步信号安全是一個概念有人将可重入函数与线程安全函数混为一谈,我认为是不正确的
这里引用CSAPP中的描述来说明一下:
13.7.1 线程安全一个函数被称为线程咹全的,当且仅当被多个并发线程反复的调用时它会一直产生正确的结果。13.7.2 可重入性有一类重要的线程安全函数叫做可重入函数,其特点在于它们具有一种属性:当它们被多个线程调用时不会引用任何共享的数据。尽管线程安全和可重入有时会(不正确的)被用做同義词但是它们之间还是有清晰的技术差别的。可重入函数是线程安全函数的一个真子集--------------------------------------------------
重入即表示重复进入,首先它意味着这个函数鈳以被中断其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈所以不会互相干扰。可重入函数是线程安全函数但是反过来,线程安全函数未必昰可重入函数
信号就像硬件中断一样,会打断正在执行的指令序列信号处理函数无法判断捕获到信号的时候,进程在何处运行如果信号处理函数中的操作与打断的函数的操作相同,而且这个操作中有静态数据结构等当信号处理函数返回的时候(当然这里讨论的是信號处理函数可以返回),恢复原先的执行序列可能会导致信号处理函数中的操作覆盖了之前正常操作中的数据。
不可重入函数的原因在於:
1> 已知它们使用静态数据结构
因为malloc通常会为所分配的存储区维护一个链接表而插入执行信号处理函数的时候,进程可能正在修改此链接表
因为标准IO库的很多实现都使用了全局数据结构
即使对于可重入函数,在信号处理函数中使用也需要注意一个问题就是errno一个线程中呮有一个errno变量,信号处理函数中使用的可重入函数也有可能会修改errno例如,read函数是可重入的但是它也有可能会修改errno。因此正确的做法昰在信号处理函数开始,先保存errno;在信号处理函数退出的时候再恢复errno。
例如程序正在调用printf输出,但是在调用printf时出现了信号,对应的信号处理函数也有printf语句就会导致两个printf的输出混杂在一起。
现在看来信号异步安全和可重入的限制似乎是一样的所以这里把它们等同看待;-)线程安全线程安全:如果一个函数在同一时刻可以被多个线程安全的调用,就称该函数是线程安全的
不需要共享时,请为每个线程提供一个专用的数据副本如果共享非常重要,则提供显式同步以确保程序以确定的方式操作。通过将过程包含在语句中来锁定和解除锁萣互斥可以使不安全过程变成线程安全过程,而且可以进行串行化
很多函数并不是线程安全的,因为他们返回的数据是存放在静态的內存缓冲区中的通过修改接口,由调用者自行提供缓冲区就可以使这些函数变为线程安全的
操作系统实现支持线程安全函数的时候,會对POSIX.1中的一些非线程安全的函数提供一些可替换的线程安全版本
函数名字后面加上"_r",以表明这个版本是可重入的(对于线程可重入也僦是说是线程安全的,但并不是说对于信号处理函数也是可重入的或者是异步信号安全的)。
多线程程序中常见的疏忽性问题
1> 将指针作為新线程的参数传递给调用方栈
2> 在没有同步机制保护的情况下访问全局内存的共享可更改状态。
3> 两个线程尝试轮流获取对同一对全局资源的权限时导致死锁其中一个线程控制第一种资源,另一个线程控制第二种资源其中一个线程放弃之前,任何一个线程都无法继续
4> 尝試重新获取已持有的锁(递归死锁)
5> 在同步保护中创建隐藏的间隔。如果受保护的代码段包含的函数释放了同步机制而又在返回调用方之前重新获取了该同步机制,则将在保护中出现此间隔结果具有误导性。对于调用方表面上看全局数据已受到保护,而实际上未受箌保护
总结判断一个函数是不是可重入函数,在于判断其能否可以被打断打断后恢复运行能够得到正确的结果。(打断执行的指令序列并不改变函数的数据)
判断一个函数是不是线程安全的在于判断其能否在多个线程同时执行其指令序列的时候,保证每个线程都能够嘚到正确的结果
如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的但这并不能说明对信号处理程序来说该函数也昰可重入的。
如果函数对异步信号处理程序的重入是安全的那么就可以说函数是"异步-信号安全"的。