这三个概念一直纠缠着我,我也时不时的会拿出来辨析下,直到昨天才发现自己可以把它们理顺了。所以学习就是这样一个反复的过程,最终达到顿悟的效果。本文主要参考APUE第三版英文版第10.6和12.5节,以及WIKI百科,还有CSDN和stackoverflow中对这些概念的讨论,然后给出一份自己认为比较合理的理解。
?
这三个概念都牵涉到异步通信,即运行中的代码不可预测什么时候会发生中断,什么时候会收到信号,什么时候会发生线程切换:
代码执行流:
#-表示代码在cpu上运行,.表示等待中
##1. 中断和信号
<f1>---------...................-----------
<f2>.........-------------------...........
###f1在执行的过程中被f2打断,当且仅当f2完整的执行完后返回f1
##2. 线程切换
<f1>----....----....----....----....----
<f2>....----....----....----....----....
###f1和f2互相打断彼此,交错地运行
?
可重入的意思就是一个函数没有执行完,又在另一个地方被调用一次,两次调用都能得到正确的结果。可重入概念在多任务操作系统之前就已经存在了,It is a concept from the time when no multitasking operating systems existed。
可重入中提到的”另一个地方”可以是:
If a function is reentrant with respect to multiple threads, we say that it is thread-safe. This doesn’t tell us, however, whether the function is reentrant with respect to signal handlers. We say that a function that is safe to be reentered from an asynchronous signal handler is async-signal safe. —According to APUE 3e Chapter 12.5
三者比较合理的关系图应该是:
红色表示不可重入函数,A+B+C表示可重入函数,其中A表示递归调用情况下可重入,C表示多线程的情况下可重入,B表示种情况下都可重入。所以单独说某函数是重入,而不限定使用场景是没有意义的;如果仅仅说是非可重入则又是有意义的…
Q:线程安全函数是可重入的吗?
A:线程安全函数是多线程情况下的可重入。但在信号或者中断中调用的情况下是不可重入的。举例如下:
一个函数通过使用mutex机制来实现多线程安全,那么这个函数如果用在信号处理函数中,那么就可能出现死锁,所以它就不是异步信号安全的。
Q:可重入函数和异步信号安全函数等同吗?
A:根据上面的关系图可以得出两者是不等同的,可重入函数在不同的情景下可以分为异步信号安全和线程安全。异步信号安全可以看作是递归调用情况下的可重入。举例如下:
//非可重入版本
int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
// hardware interrupt might invoke isr() here!!
*y = t;
}
void isr()
{
int x = 1, y = 2;
swap(&x, &y);
}
//异步信号安全版本,非线程安全
int t;
void swap(int *x, int *y)
{
int s;
s = t; // save global variable
t = *x;
*x = *y;
// hardware interrupt might invoke isr() here!
*y = t;
t = s; // restore global variable
}
void isr()
{
int x = 1, y = 2;
swap(&x, &y);
}
信号就像硬件中断一样,会打断正在执行的指令序列。信号处理函数无法判断捕获到信号的时候,进程在何处运行。如果信号处理函数中的操作与打断的函数的操作相同,而且这个操作中有静态数据结构等,当信号处理函数返回的时候(当然这里讨论的是信号处理函数可以返回),恢复原先的执行序列,可能会导致信号处理函数中的操作覆盖了之前正常操作中的数据。
所以通常函数不可重入的原因在于:
即使对于可重入函数,在信号处理函数中使用也需要注意一个问题就是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可以在多线程中使用
很多函数并不是线程安全的,因为他们返回的数据是存放在静态的内存缓冲区中的。通过修改接口,由调用者自行提供缓冲区就可以使这些函数变为线程安全的。操作系统实现支持线程安全函数的时候,会对POSIX.1中的一些非线程安全的函数提供一些可替换的线程安全版本。
例如,gethostbyname()是线程不安全的,在Linux中提供了gethostbyname_r()的线程安全实现。
函数名字后面加上”_r”,以表明这个版本是可重入的(对于线程可重入,也就是说是线程安全的,但并不是说对于信号处理函数也是可重入的,或者是异步信号安全的)。
多线程程序中常见的疏忽性问题
如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。
如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是”异步-信号安全”的。
?
原文:http://blog.csdn.net/lidonghat/article/details/52881418