[转] 多线程死锁调试小技巧
据说再高的高手在写多线程程序的时候都难确保不会产生死锁,死锁的调试也就成为一个比较常见的问题,假设有下面这样一个问题:
一个正在生产环境下运行的进程死锁了,或者你只是在跑一个程序,并没有在调试器里面打开它,然后发现没有响应,日志 输出也停止了。由于你是一个有经验的程序员,会想到“我刚刚加上了新的锁策略,不一定稳定,这可能是死锁了“。但是你不想就这么杀掉进程,因为多线程的 bug 不容易重现,遇上一次死锁可能要凭运气,错过了这次,它下次死锁可能会出现在你演示给老板看的时候……怎么办?
对于这样的问题可以借助Core Dump来调试。
什么是Core Dump?
Core的意思是内存, Dump的意思是扔出来, 堆出来.开发和使用Unix程序时, 有时程序莫名其妙的down了, 却没有任何的提示(有时候会提示core dumped). 这时候可以查看一下有没有形如core.进程号的文件生成运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump.这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考.
Core Dump又叫核心转储, 当程序没有core文件生成怎么办呢?
有时候程序down了, 但是core文件却没有生成,core文件的生成跟你当前系统的环境设置有关系, 可以用下面的语句设置一下, 然后再运行程序便会生成core文件.
ulimit -c unlimited
通过ulimit -a查看core file size设置,应为:core file size (blocks, -c) unlimited
core文件生成的位置一般于运行程序的路径相同, 文件名一般为core.进程号,在我的ubuntu12.04lts下生产的文件名为core。
修改core_pattern指定生成core file的位置,比如:
echo "/tmp/core.%e.%p.%s.%t" > /proc/sys/kernel/core_pattern
介绍了core dump之后,来看看如何在多线程调试中使用core dump。
使用 kill 命令产生 core dump文件:
kill -11 pid
这不还是杀掉进程嘛?没错,但是你用信号11杀掉它,会让进程产生一个 Segmentation Fault,从而(如果你没禁用 core dump 的话),导致一个 core dump。随后你得到一个 core 文件,里面包含了死锁的时候,进程的内存镜像,也就包括了正在纠结缠绵,生离死别从而产生死锁的那两个,没准是几个,线程们的,栈。
现在知道该怎么办了吧?用 gdb 打开这个 core 文件,然后
thread apply all bt
gdb 会打出所有线程的栈,如果你发现有那么几个栈停在 pthread_wait 或者类似调用上,大致就可以得出结论:就是它们几个儿女情长,耽误了整个进程。
下面我来举一个简单的例子(为了代码尽量简单,使用了C++11的thread library)
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
mutex m1,m2;
void func_2()
{
m2.lock();
cout<< "about to dead_lock"<<endl;
m1.lock();
}
void func_1()
{
m1.lock();
chrono::milliseconds dura( 1000 );// delay to trigger dead_lock
this_thread::sleep_for( dura );
m2.lock();
}
int main()
{
thread t1(func_1);
thread t2(func_2);
t1.join();
t2.join();
return 0;
}
编译代码
$> g++ -Wall -std=c++11 dead_lock_demo.cpp -o dead_lock_demo -g -pthread
运行程序,发现程序打印出“about to dead_lock” 就不动了,现在我们使用gdb来调试。注意gdb的版本要高于7.0,之前使用过gdb6.3调试多线程是不行的。
在这之前需要先产生core dump文件:
$> ps -aux | grep dead_lock_demo
找出 dead_lock_demo 线程号,然后:
$> kill -11 pid
此时会生成core dump 文件,在我的系统上名字就是 core
然后调试:
$> gdb dead_lock_demo core
$> thread apply all bt
下面来看一下实际的过程:
从上图可以看出两个线程都阻塞在wait上,而且还给出了在哪一行代码中,很容易就定位到产生死锁的位置。
手动强制某个进程产生core dump的方法
当某些程序发生crash时,对应进程会产生coredump文件。通过这个coredump文件,开发人员可以找到bug的原因。但是coredump的产生,大都是因为程序crash了。
而有些bug是不会导致程序crash的,比如死锁,这时程序已经不正常了,可是却没有coredump产生。如果环境又不允许gdb调试,难道我们就束手无策了吗?
针 对以上这种情况,一般情况下,对于这样的进程可以利用 watchdog监控它们,当发现这些进程很长时间没有更新其heartbeat时,可以给这些进程发送可以导致其产生coredump的信号。根据 linux的信号默认的处理行为,SIGQUIT,SIGABRT, SIGFPE和SIGSEGV都可以让该进程产生coredump文件。这样我们可以通过coredump来得知死锁是否发生。 当然, 如果进程添加了这些信号的处理函数,那么就不会产生coredump了。不过,对于SIGQUIT, SIGABRT, SIGFPE, SIGSEGV,有谁会为它们加上信号处理函数呢。
查看Signal的默认操作,SIGQUIT:由用户产生退出符(Ctrl-\),默认是终止且进行core dump。
还 有一种情况,进程并没有死锁或者block在某个位置, 但是我们需要在某个指定位置进行调试,获取某些变量或者其它信息。但是,有可能是客户环境或者生产环境,不允许我们进行长时间的检测。那么,我们就需要通 过coredump来获得进程在运行到该点时的快照。 这个时候,可以利用gdb来手工产生coredump。在attach上这个进程时,在指定位置打上断点,当断点触发时,使用gdb的命令gcore,可 以立即产生一个coredump。 这样,我们就拿到了这个位置的进程快照。
Core Dump 如何产生
上面说当程序运行过程中异常终止或崩溃时会发生 core dump,但还没说到什么具体的情景程序会发生异常终止或崩溃,例如我们使用 kill -9
命令杀死一个进程会发生 core dump 吗?实验证明是不能的,那么什么情况会产生呢?
Linux 中信号是一种异步事件处理的机制,每种信号对应有其默认的操作,你可以在 这里 查 看 Linux 系统提供的信号以及默认处理。默认操作主要包括忽略该信号(Ingore)、暂停进程(Stop)、终止进程(Terminate)、终止并发生core dump(core)等。如果我们信号均是采用默认操作,那么,以下列出几种信号,它们在发生时会产生 core dump:
Signal | Action | Comment |
---|---|---|
SIGQUIT | Core | Quit from keyboard |
SIGILL | Core | Illegal Instruction |
SIGABRT | Core | Abort signal from abort |
SIGSEGV | Core | Invalid memory reference |
SIGTRAP | Core | Trace/breakpoint trap |
当然不仅限于上面的几种信号。这就是为什么我们使用 Ctrl+z
来挂起一个进程或者 Ctrl+C
结束一个进程均不会产生 core dump,因为前者会向进程发出 SIGTSTP 信号,该信号的默认操作为暂停进程(Stop Process);后者会向进程发出SIGINT 信号,该信号默认操作为终止进程(Terminate Process)。同样上面提到的 kill -9
命令会发出 SIGKILL 命令,该命令默认为终止进程。而如果我们使用 Ctrl+\
来终止一个进程,会向进程发出 SIGQUIT 信号,默认是会产生 core dump 的。还有其它情景会产生 core dump, 如:程序调用 abort()
函数、访存错误、非法指令等等。
下面举两个例子来说明:
-
终端下比较
Ctrl+C
和Ctrl+\
:guohailin@guohailin:~$ sleep 10 #使用sleep命令休眠 10 s ^C #使用 Ctrl+C 终止该程序,不会产生 core dump guohailin@guohailin:~$ sleep 10 ^\Quit (core dumped) #使用 Ctrl+\ 退出程序, 会产生 core dump guohailin@guohailin:~$ ls #多出下面一个 core 文件 -rw------- 1 guohailin guohailin 335872 10月 22 11:31 sleep.core.21990