这周有位新同事请我帮忙看一个关于信号处理的问题,程序希望在收到一个信号后退出,而他在信号处理方法里却做了许多事,包括释放一些全局内存等。
这样问题就产生了,程序不定时的就挂死了,用gdb一看,所有的线程都挂在了pthread_once方法里,而似乎每个线程都在处理信号,其中产生问题的线程堆栈如下:
Thread 1 (Thread 0x7f41252f3720 (LWP 31542)):
#0 0x000000339860cb1b in pthread_once () from /lib64/libpthread.so.0
#1 0x00000033982fd6f4 in backtrace () from /lib64/libc.so.6
#2 0x000000339826fa4b in __libc_message () from /lib64/libc.so.6
#3 0x0000003398275366 in malloc_printerr () from /lib64/libc.so.6
#4 0x0000003398278de4 in _int_malloc () from /lib64/libc.so.6
#5 0x0000003398279b91 in malloc () from /lib64/libc.so.6
#6 0x00007f41253b40bd in operator new(unsigned long) () from /usr/lib64/libstdc++.so.6
#7 0x00007f41253b41d9 in operator new[](unsigned long) () from /usr/lib64/libstdc++.so.6
---Type <return> to continue, or q <return> to quit---
#8 0x000000000045f86a in log4cpp::StringUtil::vform(char const*, __va_list_tag*) ()
#9 0x000000000044eb69 in log4cpp::Category::_logUnconditionally(int, char const*, __va_list_tag*) ()
#10 0x000000000044f4af in log4cpp::Category::warn(char const*, ...) ()
#11 0x00000000004431a1 in singalHandler(int) ()
#12 <signal handler called>
#13 0x000000339860cb19 in pthread_once () from /lib64/libpthread.so.0
#14 0x00000033982fd6f4 in backtrace () from /lib64/libc.so.6
#15 0x000000339826fa4b in __libc_message () from /lib64/libc.so.6
#16 0x0000003398275366 in malloc_printerr () from /lib64/libc.so.6
#17 0x0000003398278de4 in _int_malloc () from /lib64/libc.so
问题在哪里呢?似乎所有开源代码里,都少有人在信号处理方法里写大量代码的,这是为什么呢?
原因在于,信号是可能在任意时刻打断你线程的正在执行代码,信号处理方法插入进去执行时,就可能造成有些函数被反复重入。例如上面这个例子中,thead1正在new一个对象,执行malloc分配内存的过程中,突然被信号打断,而信号处理方法里居然又有malloc过程,而malloc是不能反复重入的!于是导致挂死。
另一个问题的,子进程会继承父进程的很多资源,其中就包括信号,他的程序处理信号后,才pthread_create许多工作线程,而且,没有屏蔽信号,所以,所有的线程都在处理那个信号处理方法,所有线程都挂死了。
解决方法有很多种,通常是在信号处理方法里只做少量工作,通知其他线程自我回收资源。
对于多线程程序来说,只弄一个线程使用阻塞式信号处理方法,专职的处理信号,这样更符合多线程的设计精神。例如,在派生子线程前,用pthread_sigmask来设置信号不会打断子线程的运行,而在主线程里,使用阻塞的sigwait方法来同步处理信号,在这里可以处理一些复杂的操作,不用担心“重入”问题。