[转] 多线程死锁调试小技巧

据说再高的高手在写多线程程序的时候都难确保不会产生死锁,死锁的调试也就成为一个比较常见的问题,假设有下面这样一个问题:

  一个正在生产环境下运行的进程死锁了,或者你只是在跑一个程序,并没有在调试器里面打开它,然后发现没有响应,日志 输出也停止了。由于你是一个有经验的程序员,会想到“我刚刚加上了新的锁策略,不一定稳定,这可能是死锁了“。但是你不想就这么杀掉进程,因为多线程的 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。 这样,我们就拿到了这个位置的进程快照。

时间: 2024-10-13 16:12:54

[转] 多线程死锁调试小技巧的相关文章

&lt;python&gt;If 语句、while语句的规则和调试小技巧

If 语句的规则 1. 每一个“if 语句”必须包含一个 else. 2. 如果这个 else 永远都不应该被执行到,因为它本身没有任何意义,那你必须在 else 语句后面 使用一个叫做 die 的函数,让它打印出错误信息并且死给你看,这和上一节的习题类似,这样你 可以找到很多的错误. 3. “if 语句”的嵌套不要超过 2 层,最好尽量保持只有 1 层. 这意味着如果你在 if 里边又有了 一个 if,那你就需要把第二个 if 移到另一个函数里面. 4. 将“if 语句”当做段落来对待,其中的

JavaScript的兼容小坑和调试小技巧

JavaScript作为一种弱类型编程语言,入门简单,只要稍微注意一下IE方面的兼容性,就可以很好的使用它. 本文主要是对IE兼容的小坑和调试的小技巧进行举例分析,并给出解决方法. 1.var str: 与 var str = "":的区别 1 <script> 2 var Str1; 3 for (var i = 0; i < 3; i++) 4 { Str1 += "xxx" } 5 alert(Str1); 6 var Str2 = &quo

【调试】Visual Studio 调试小技巧(2)-从查看窗口得到更多信息(转载)

在使用Visual Studio开发调试程序时,我们经常需要打开查看窗口(Watch)来分析变量.有时在查看窗口显示的内容不是很直观.为了能从查看窗口的变量中得到更多的信息,我们需要一些小的技巧.下面是几个例子. 1. Windows消息 在开发调试界面程序时,我们经常需要查看消息的内容.当在查看窗口中,仅仅只显示出消息对应的整数值,我们并不能直观地知道究竟是什么消息.比如: 为了能让查看窗口显示出消息内容,我们只需要在变量名添加“, wm”的格式化后缀,就能显示消息的名字: 2. 句柄返回值

Firebug折腾记_(2)HTML&amp;CSS定位及调试小技巧

题外话 传统的开发我们是在编辑器操作代码保存,再到浏览器预览查看效果的; 而如今的firebug和chrome的内置调试器就不需要了..可以直接实时编辑且看到效果; 在调试中对代码的操作不会保存到本地实际代码中..是不是很赞!!; 定位HTML元素的三种方式 进入调试工具界面,按下"瓢虫"旁边的小鼠标,再进行网页元素的选择 默认快捷键,Ctrl + shift + C 鼠标移动到网页的某一块元素,鼠标右键,使用Firebug查看元素 HTML及CSS简单调试 ##HTML元素编辑 除了

调试小技巧

谷歌浏览器devtools 中的调试技巧 Ruby代码调试 如果不小心敲错了 ruby 代码,比如 create.js.erb中把?render?写成了?rennder?那么点击"发布评论"按钮是看不到报错信息的. 可以打开 devtools -> console ,这里可以看到一个 500 的错误.具体的错误内容可以到 network 下面,再次提交以下评论,就能看到了. 那么不知道错误信息,我们肯定很难修改,于是我们到如下位置查看错误信息: 如上是查看错误类型,那么具体的错误

CSS调试小技巧 —— 调试DOM元素hover,focus,actived的样式

最近学习html5和一些UI框架,接触css比较多,就来跟大家分享一下css中的一些调试技巧.之前做页面,css都是自己写的,所以要改哪里可以很快的找到,现在使用了UI框架,里面的样式是不可能读完的,所以就要通过调试来找到要修改的地方. 在调试CSS的时候,我们一般使用Chrome.Firefox.IE等浏览器自带的工具,快捷键都为F12.但是,我们要调试如hover的样式时,鼠标放上去才会显示,鼠标一走就看不到了,没办法看清楚css样式,还有就是浏览器自带的一些hover.foucs.acti

你不知道的 IDEA Debug调试小技巧

一.多线程调试断点 Intellij IDEA 的debug断点调试是有一个模式的选择的,就像下面这张图,平时我们都使用的是默认的 ALL(在Eclipse中默认是线程模式) ,这种模式我们只能将一个线程断下来,但其他线程却已经执行过了:而将其改为 Thread 后,就可以多个线程都断下来,并且可以很方便的切换线程的执行流程,这就是多线程调试. 在debug控制台能够很方便的查看线程的执行状态,也可以很方便的选择某个线程去执行: 二.回退断点 不知道大伙有没有遇到这样一个问题,当我们进行debu

Jmeter脚本调试小技巧

使用Jmeter开发脚本时,在使用前置或后置处理器进行关联时,难免需要调试,这时可以使用Jmeter的Debug Sampler,示例如下: 第一步:设置后置处理器及需要提取的动态值 1.添加一个http sampler,访问:www.baidu.com; 2.给这个sampler添加一个后置处理器--Regular Expression Extractor,内容如下图: 第二步:提取第一步中后置处理器设置的值 1.在Thread Group下,添加一个Debug Sampler 2.设置Deb

Android——安卓调试小技巧(一)

在我们做完一个Activity之后,总是要看下效果才放心,但是面对虚拟器的启动慢,反应慢的两个痛点,我们总要寻找别的方法. 1,copy生成的APK文件安装 在我们选择Run As之后,会在bin文件夹下生产一个app的apk文件,我们可以拷贝出来,在未发布到google之前,发给测试人员使用. 或者是: 在这个apk文件上右键选择打开,之后会启用我电脑上默认的应用安装器(建议大家都在电脑上安装一个应用安装器),通过USB线安装到手机上.前提是,手机开启了调试模式. 2,DDMS+真机 玩儿过P