這周有位新同事請我幫忙看一個關於信號處理的問題,程序希望在收到一個信號後退出,而他在信號處理方法裡卻做了許多事,包括釋放一些全局內存等。
這樣問題就產生了,程序不定時的就掛死了,用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
更多精彩內容:http://www.bianceng.cn/OS/Linux/
#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方法來同步處理信號,在這裡可以處理一些復雜的操作,不用擔心“重入”問題。