产生死锁的四个必要条件
(1) 互斥条件:一个资源每次只能被一个进程(线程)使用。
(2) 请求与保持条件:一个进程(线程)因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件 : 此进程(线程)已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件 : 多个进程(线程)之间形成一种头尾相接的循环等待资源关系。
可以使用 jstack或者pstack 和 gdb 工具对死锁程序进行分析。
pstack: 功能是打印输出此进程的堆栈信息。可以输出所有线程的调用关系栈
jstack:jstack是java虚拟机自带的一种堆栈跟踪工具,所以仅适用于java程序,功能跟pstack一样,但是更强大,可以提示哪个地方可能死锁了。
pstack和jstack判断死锁,都需要多执行几次命令,观察每次的输出结果,才能推测是否死锁了。
gdb:
1 运行程序,设置能影响程序运行的参数和环境 ;
2 控制程序在指定的条件下停止运行;
3 当程序停止时,可以检查程序的状态;
4 当程序 crash 时,可以检查 core 文件;
5 可以修改程序的错误,并重新运行程序;
6 可以动态监视程序中变量的值;
7 可以单步执行代码,观察程序的运行状态。
线程死锁分析:
1. 连续多次执行 $pstack <PID> 其中PID是进程号
查看每个线程的函数调用关系的堆栈,观察每个线程当前的执行点是否在等待一个锁。
多次执行该命令,发现某些线程的当前执行点不变,总是在等待同一个锁,就可以怀疑是否死锁了。
如果怀疑哪些线程发生死锁了,可以采用gdb 进一步attach线程并进行分析。
2. 执行$gdb -p <PID> 或者 $gdb attach <PID>
(gdb) info thread
(gdb) thread <thread ID>
BTW:gdb的all-stop和non-stop模式 (GDBv7.0及之上版本支持)
默认都是all-stop,即在某线程的代码里设置了断点,触发断点时所有的线程都会中断。
non-stop是多线程调试的好帮手,某个线程里的断点触发了,其它线程还照常run,可以重现死锁的场景。
想打开non-stop,可以修改~/.gdbinit,添加如下三行:
set target-async 1
set pagination off
set non-stop on
或者在进入gdb以后,依次执行以上三行,可以临时开启non-stop,退出gdb后失效。(猜是这样,需要实验验证)
另外,如果多个线程走的一个代码块,停在了同一个断点上,例如只想让线程1和线程3继续,使用:
(gdb) thread apply 3 1 continue
再BTW,判断热锁。热锁是指经常被打开关闭打开关闭的锁,由于多个线程对锁或者临界区的竞争造成的。
* 频繁的线程的上下文切换:从操作系统对线程的调度来看,当 线程在等待资源而阻塞的时候,操作系统会将之切换出来,放到等待的队列,当线程获得资源之后,调度算法会将这个线程切换进去,放到执行队列中。
* 大量的系统调用:因为线程的上下文切换,以及热锁的竞争,或 者临界区的频繁的进出,都可能导致大量的系统调用。
* 大部分 CPU开销用在 “系统态 ”:线程上下文切换,和系统调用,都会导致 CPU在 “系统态 ”运行,换而言之,虽然系统很忙碌,但是 CPU用在 “用户态 ”的比例较小,应用程序得不到充分的 CPU资源。
* 随着 CPU数目的增多,系统的性能反而下降。因为 CPU数目多,同 时运行的线程就越多,可能就会造成更频繁的线程上下文切换和系统态的 CPU开销,从而导致更糟糕的性能。
可以通过脚本来固定时间间隔调用pstack,和查看系统资源使用情况的命令,比如top,分别将输出打印到log文件里。
当然要每次执行命令都得打印时间戳。
分析log文件,可以大概推测出热锁所在的位置。可以通过优化热锁,来提升程序的性能,例如吞吐量、响应时间。
原文地址:https://www.cnblogs.com/ryn3316/p/9404698.html