#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
void gotsig(int n)
{
printf("haha");
}
int main()
{
struct itimerval value;
struct sigaction sact;
sigemptyset( &sact.sa_mask );
sact.sa_flags = 0;
sact.sa_handler = gotsig;
sigaction(SIGALRM,&sact,NULL);
value.it_interval.tv_sec = 0;
value.it_interval.tv_usec = 100000;
value.it_value.tv_sec = 0;
value.it_value.tv_usec = 1000;
setitimer(ITIMER_REAL, &value,NULL);
while(1) {
/* other code here */
printf("HA~");
}
}
这个代码的意思就是每一毫秒发送给自己一次SIGALRM信号,然后一直打印
这个代码有什么问题,咋一看,看不出来有什么问题吧(如果你一眼就看出问题来,那么不要再往下阅读了^^)
答案是,很可能会死锁,死锁有几个必要条件,我要得到资源a的时候被阻塞,而能释放a的那个家伙又在等待我已经拥有的资源。
那么这里资源在哪呢,在printf的锁里,是什么东西,我咋不知道呢??
众所周知,操作系统支持多线程,而且标准IO是有缓冲的,显然stdout是全局资源(文件是属于进程的资源,是所有线程共享的,就像地址空间一样). 那么多线程printf的时候会发生什么?很显然,缓冲区的概念(消费者和生产者的故事应该知道)出来了,必须得有锁,否则缓冲区可能会出问题的(一个打印aaa,一个打印bbb,最后有可能打印aabbba)
这样的锁如果有,就表明printf是线程安全的,否则就不是线程安全的!
显然如果标准库的printf一族如果不是线程安全的,那么线程中就得自己加锁了,多麻烦的事情(虽然C标准没有线程的概念,但是我的标准库是线程安全的);
这样printf的行为大概为
int printf(const char *fmt,...)
{
/**/
加锁
tag1:
操作缓冲,如果必要write(1,buf,size);
释放锁
/**/
}
这个锁肯定是所有线程可见的,这个函数不可重入,在信号处理中调用的时候,被中断的地方可能正是加锁和解锁之间的位置,这样信号处理中的printf进行同样的加锁解锁过程,因为那时候它已经拥有了锁(在被打断的时候),再去要锁,那么肯定得不到,信号处理中的printf永远阻塞在那锁的获取代码上,永远不会返回,信号处理函数也就永远不会返回,显然死锁了
strace ./a.out 会发现阻塞在futex上,这个系统调用正是我的系统上锁的实现所使用的(pthread_mutex_lock)
目前的死锁还只是这个线程,当别的线程调用printf时,也死锁了
总结一下
信号处理中不能调用不可重入函数,带有锁的函数是不可重入的
printf是线程安全的,同理,malloc也是,只是malloc操作地址空间,printf操作文件而已,都是全局资源,都有锁的
这只是个我遇到的情况,
|