可重入 = 线程安全 + 异步信号安全

线程安全

多个线程在同个时间段并发执行,一定可以得到正确的结果。

可以通过对线程间竞争的临界资源加锁保护来达到线程安全的目的。

可重入

执行过程中被打断后恢复继续执行,一定还可以得到正确的结果。

被打断的情况比如信号的软中断线程间的切换,而线程并发就是基于线程间的切换,因此可重入一定是线程安全的,反之不一定。

异步信号安全

我认为是指,执行过程中信号中断执行信号处理程序返回继续执行后一定还可以得到正确的结果,也和线程切换一样相当于可重入的一个具体实例。

这个可以通过信号屏蔽来达到异步信号安全。在APUE里还提到将异步信号处理利用sigwait()函数实现为只在一个线程内同步处理,这个技巧要学习学习。

示例

总而言之,可重入包含了线程安全异步信号安全。下面举一个关于printf()函数的示例,我觉得挺有意思的。

示例来源:http://blog.csdn.net/littlehedgehog/article/details/4104210

printf()是带缓冲区的标准IO库函数,缓冲区是所有线程共享的,而printf()函数要求是线程安全函数,那么具体实现就可能对缓冲区用一个互斥量进行保护。

这样printf()函数就是线程安全的,不过却不是异步信号安全的。

因为假设这么一种情况,如果一个线程执行printf()时在对缓冲区上锁后被一个信号中断,而信号处理程序里也调用了printf()函数这时需要获取保护缓冲区的互斥量锁,那么就出现死锁了。当然前提是这个互斥量的类型属性不是RECURSIVE,不过即使允许递归加锁,这样虽然不会引起死锁,但缓冲区存放的内容可能是被穿插的,结果就不会是正确的。

可以写个程序来试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
void sig_fun(int sig)
{
printf("**********\n");
}
int main()
{
signal(SIGALRM, sig_fun);
struct itimerval val;
val.it_interval.tv_sec = 0;
val.it_interval.tv_usec = 1;
val.it_value.tv_sec = 0;
val.it_value.tv_usec = 1000;
setitimer(ITIMER_REAL, &val, NULL);
while(1) {
printf("..........\n");
}
return 0;
}

这个程序不停调用printf()进行输出,并利用setitimer()函数每隔1微秒发送ALARM信号,而ALARM信号处理程序里也调用了printf()函数。执行该程序,可以看到输出一会儿忽然停住了,这时就是出现死锁了。