遇到一个死锁,堆栈类似如下
#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 之类的函数。