遇到一个死锁,堆栈类似如下
#0 0x00002b6b7d16cc38 in __lll_mutex_lock_wait () from /lib64/libc.so.6 #1 0x00002b6b7d126a9d in _L_lock_1769 () from /lib64/libc.so.6 #2 0x00002b6b7d126876 in __tz_convert () from /lib64/libc.so.6
搜了一下,发现之前有人已经讨论过这个问题了,在这里,http://lists.gnu.org/archive/h… ,他给出了一个样例程序如下
#include <sys/time.h> #include <time.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> volatile char *r; void handler(int sig) { time_t t; time(&t); r = ctime(&t); } int main() { struct itimerval it; struct sigaction sa; time_t t; int counter = 0; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handler; sigaction(SIGALRM, &sa, NULL); it.it_value.tv_sec = 0; it.it_value.tv_usec = 1000; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 1000; setitimer(ITIMER_REAL, &it, NULL); while(1) { counter++; time(&t); r = ctime(&t); printf("Loop %d\n",counter); } return 0; }
这个程序可以稳定的重现死锁的问题,gdb attach 上去之后看到的堆栈也是类似的,一开始以为是 ctime 的可重入问题,觉得改成 ctime_r 就可以很容易的解决,动手之后发现修改 ctime_r 并不奏效
另外,下面这个代码,却又不会引起死锁
#include <stdio.h> #include <unistd.h> #include <pthread.h> #include <stdlib.h> int i = 0; volatile char* r; void* thread(void*) { while (1) { time_t t = time(NULL); r = ctime(&t); printf("%d\n", i++); } return NULL; } int main() { pthread_t tid; for (int j = 0; j < 100; j++) { pthread_create(&tid, NULL, thread, NULL); } sleep(1); return 0; }
在其他地方,也有见到类似的讨论,例如这里,https://bugs.debian.org/cgi-bi… ,说道
According to signal(7), ctime is not safe to be called from a signal
handler (nor, more generally, are malloc or free). Probably debug messages
from signal handler context should not attempt to display the time, or
should use some built-in time implementation that doesn’t malloc or care
about timezones, or something.
另外,这里,http://stackoverflow.com/quest… ,https://bugzilla.redhat.com/sh… ,https://bugzilla.redhat.com/sh… ,也都有讨论
现在的问题是
- 为什么在那个信号处理的例子中会死锁,而多线程的不会
- 对于死锁的那个例子,怎么避免
经过 kamus 大哥指点,这个其实不是 ctime 本身的问题,而是 ctime 内部使用了 malloc,通过 strace 分析可以发现 ctime 的系统调用如下
brk(0) = 0x501000 brk(0x522000) = 0x522000 open("/etc/localtime", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=165, ...}) = 0 fstat(3, {st_mode=S_IFREG|0644, st_size=165, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b5a8a93c000 read(3, "TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\0\0\0\3\0"..., 4096) = 165 close(3) = 0 munmap(0x2b5a8a93c000, 4096) = 0
至于信号处理和多线程,这个就跟递归是类似,不能递归的对同一把锁上锁,但是可以多线程对同一把锁上锁,只要其他线程稍后把这个锁释放了就可以了,最后,解决方法就是,不要在信号处理的函数中使用 ctime 或者 malloc 之类的函数。