【转】一个 Linux 上分析死锁的简单方法
简介
死锁 (deallocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。 由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程(线程)在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
一种交叉持锁死锁的情形,此时执行程序中两个或多个线程发生永久堵塞(等待),每个线程都在等待被其它线程占用并堵塞了的资源。例如,如果线程 1 锁住了记录 A 并等待记录 B,而线程 2 锁住了记录 B 并等待记录 A,这样两个线程就发生了死锁现象。在计算机系统中 , 如果系统的资源分配策略不当,更常见的可能是程序员写的程序有错误等,则会导致进程因竞争资源不当而产生死锁的现象。
产生死锁的四个必要条件
(1) 互斥条件:一个资源每次只能被一个进程(线程)使用。
(2) 请求与保持条件:一个进程(线程)因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件 : 此进程(线程)已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件 : 多个进程(线程)之间形成一种头尾相接的循环等待资源关系。
图 1. 交叉持锁的死锁示意图:
注释:在执行 func2 和 func4 之后,子线程 1 获得了锁 A,正试图获得锁 B,但是子线程 2 此时获得了锁 B,正试图获得锁 A,所以子线程 1 和子线程 2 将没有办法得到锁 A 和锁 B,因为它们各自被对方占有,永远不会释放,所以发生了死锁的现象。
使用 pstack 和 gdb 工具对死锁程序进行分析
pstack 在 Linux 平台上的简单介绍
pstack 是 Linux(比如 Red Hat Linux 系统、Ubuntu Linux 系统等)下一个很有用的工具,它的功能是打印输出此进程的堆栈信息。可以输出所有线程的调用关系栈。
gdb 在 Linux 平台上的简单介绍
GDB 是 GNU 开源组织发布的一个强大的 UNIX 下的程序调试工具。Linux 系统中包含了 GNU 调试程序 gdb,它是一个用来调试 C 和 C++ 程序的调试器。可以使程序开发者在程序运行时观察程序的内部结构和内存的使用情况 .
gdb 所提供的一些主要功能如下所示:
1 运行程序,设置能影响程序运行的参数和环境 ;
2 控制程序在指定的条件下停止运行;
3 当程序停止时,可以检查程序的状态;
4 当程序 crash 时,可以检查 core 文件;
5 可以修改程序的错误,并重新运行程序;
6 可以动态监视程序中变量的值;
7 可以单步执行代码,观察程序的运行状态。
gdb 程序调试的对象是可执行文件或者进程,而不是程序的源代码文件。然而,并不是所有的可执行文件都可以用 gdb 调试。如果要让产生的可执行文件可以用来调试,需在执行 g++(gcc)指令编译程序时,加上 -g 参数,指定程序在编译时包含调试信息。调试信息包含程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb 利用这些信息使源代码和机器码相关联。gdb 的基本命令较多,不做详细介绍,大家如果需要进一步了解,请参见 gdb 手册。
清单 1. 测试程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
#include
#include
#include
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;
static int sequence1 = 0;
static int sequence2 = 0;
int func1()
{
pthread_mutex_lock(&mutex1);
++sequence1;
sleep(1);
pthread_mutex_lock(&mutex2);
++sequence2;
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return sequence1;
}
int func2()
{
pthread_mutex_lock(&mutex2);
++sequence2;
sleep(1);
pthread_mutex_lock(&mutex1);
++sequence1;
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return sequence2;
}
void* thread1(void* arg)
{
while (1)
{
int iRetValue = func1();
if (iRetValue == 100000)
{
pthread_exit(NULL);
}
}
}
void* thread2(void* arg)
{
while (1)
{
int iRetValue = func2();
if (iRetValue == 100000)
{
pthread_exit(NULL);
}
}
}
void* thread3(void* arg)
{
while (1)
{
sleep(1);
char szBuf[128];
memset(szBuf, 0, sizeof(szBuf));
strcpy(szBuf, "thread3");
}
}
void* thread4(void* arg)
{
while (1)
{
sleep(1);
char szBuf[128];
memset(szBuf, 0, sizeof(szBuf));
strcpy(szBuf, "thread3");
}
}
int main()
{
pthread_t tid[4];
if (pthread_create(&tid[0], NULL, &thread1, NULL) != 0)
{
_exit(1);
}
if (pthread_create(&tid[1], NULL, &thread2, NULL) != 0)
{
_exit(1);
}
if (pthread_create(&tid[2], NULL, &thread3, NULL) != 0)
{
_exit(1);
}
if (pthread_create(&tid[3], NULL, &thread4, NULL) != 0)
{
_exit(1);
}
sleep(5);
//pthread_cancel(tid[0]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_join(tid[2], NULL);
pthread_join(tid[3], NULL);
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
pthread_mutex_destroy(&mutex3);
pthread_mutex_destroy(&mutex4);
return 0;
}
|
清单 2. 编译测试程序
1
|
[dyu@xilinuxbldsrv purify]$ g++ -g lock.cpp -o lock -lpthread
|
清单 3. 查找测试程序的进程号
1
2
|
[dyu@xilinuxbldsrv purify]$ ps -ef|grep lock
dyu 6721 5751 0 15:21 pts/3 00:00:00 ./lock
|
清单 4. 对死锁进程第一次执行 pstack(pstack –进程号)的输出结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
[dyu@xilinuxbldsrv purify]$ pstack 6721
Thread 5 (Thread 0x41e37940 (LWP 6722)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a9b in func1() ()
#4 0x0000000000400ad7 in thread1(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 4 (Thread 0x42838940 (LWP 6723)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a17 in func2() ()
#4 0x0000000000400a53 in thread2(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x43239940 (LWP 6724)):
#0 0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6
#1 0x0000003d19c9a364 in sleep () from /lib64/libc.so.6
#2 0x00000000004009bc in thread3(void*) ()
#3 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#4 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x43c3a940 (LWP 6725)):
#0 0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6
#1 0x0000003d19c9a364 in sleep () from /lib64/libc.so.6
#2 0x0000000000400976 in thread4(void*) ()
#3 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#4 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2b984ecabd90 (LWP 6721)):
#0 0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0
#1 0x0000000000400900 in main ()
|
清单 5. 对死锁进程第二次执行 pstack(pstack –进程号)的输出结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
[dyu@xilinuxbldsrv purify]$ pstack 6721
Thread 5 (Thread 0x40bd6940 (LWP 6722)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a87 in func1() ()
#4 0x0000000000400ac3 in thread1(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 4 (Thread 0x415d7940 (LWP 6723)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a03 in func2() ()
#4 0x0000000000400a3f in thread2(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x41fd8940 (LWP 6724)):
#0 0x0000003d19c7aec2 in memset () from /lib64/libc.so.6
#1 0x00000000004009be in thread3(void*) ()
#2 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#3 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x429d9940 (LWP 6725)):
#0 0x0000003d19c7ae0d in memset () from /lib64/libc.so.6
#1 0x0000000000400982 in thread4(void*) ()
#2 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#3 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2af906fd9d90 (LWP 6721)):
#0 0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0
#1 0x0000000000400900 in main ()
|
连续多次查看这个进程的函数调用关系堆栈进行分析:当进程吊死时,多次使用 pstack 查看进程的函数调用堆栈,死锁线程将一直处于等锁的状态,对比多次的函数调用堆栈输出结果,确定哪两个线程(或者几个线程)一直没有变化且一直处于等锁的状态(可能存在两个线程 一直没有变化)。
输出分析:
根据上面的输出对比可以发现,线程 1 和线程 2 由第一次 pstack 输出的处在 sleep 函数变化为第二次 pstack 输出的处在 memset 函数。但是线程 4 和线程 5 一直处在等锁状态(pthread_mutex_lock),在连续两次的 pstack 信息输出中没有变化,所以我们可以推测线程 4 和线程 5 发生了死锁。
Gdb into thread
输出:
清单 6. 然后通过 gdb attach 到死锁进程
1
2
3
4
5
6
7
8
9
10
11
|
(gdb) info thread
5 Thread 0x41e37940 (LWP 6722) 0x0000003d1a80d4c4 in __lll_lock_wait ()
from /lib64/libpthread.so.0
4 Thread 0x42838940 (LWP 6723) 0x0000003d1a80d4c4 in __lll_lock_wait ()
from /lib64/libpthread.so.0
3 Thread 0x43239940 (LWP 6724) 0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
2 Thread 0x43c3a940 (LWP 6725) 0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
* 1 Thread 0x2b984ecabd90 (LWP 6721) 0x0000003d1a807b35 in pthread_join ()
from /lib64/libpthread.so.0
|
清单 7. 切换到线程 5 的输出
1
2
3
4
5
6
7
8
9
10
11
|
(gdb) thread 5
[Switching to thread 5 (Thread 0x41e37940 (LWP 6722))]#0 0x0000003d1a80d4c4 in
__lll_lock_wait () from /lib64/libpthread.so.0
(gdb) where
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a9b in func1 () at lock.cpp:18
#4 0x0000000000400ad7 in thread1 (arg=0x0) at lock.cpp:43
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
|
清单 8. 线程 4 和线程 5 的输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
(gdb) f 3
#3 0x0000000000400a9b in func1 () at lock.cpp:18
18 pthread_mutex_lock(&mutex2);
(gdb) thread 4
[Switching to thread 4 (Thread 0x42838940 (LWP 6723))]#0 0x0000003d1a80d4c4 in
__lll_lock_wait () from /lib64/libpthread.so.0
(gdb) f 3
#3 0x0000000000400a17 in func2 () at lock.cpp:31
31 pthread_mutex_lock(&mutex1);
(gdb) p mutex1
$1 = {__data = {__lock = 2, __count = 0, __owner = 6722, __nusers = 1, __kind = 0,
__spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = "0200000000000000B32000001", '00'
, __align = 2}
(gdb) p mutex3
$2 = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = '00' , __align = 0}
(gdb) p mutex2
$3 = {__data = {__lock = 2, __count = 0, __owner = 6723, __nusers = 1,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = "0200000000000000C32000001", '00'
, __align = 2}
(gdb)
|
从上面可以发现,线程 4 正试图获得锁 mutex1,但是锁 mutex1 已经被 LWP 为 6722 的线程得到(__owner = 6722),线程 5 正试图获得锁 mutex2,但是锁 mutex2 已经被 LWP 为 6723 的 得到(__owner = 6723),从 pstack 的输出可以发现,LWP 6722 与线程 5 是对应的,LWP 6723 与线程 4 是对应的。所以我们可以得出, 线程 4 和线程 5 发生了交叉持锁的死锁现象。查看线程的源代码发现,线程 4 和线程 5 同时使用 mutex1 和 mutex2,且申请顺序不合理。
总结
本文简单介绍了一种在 Linux 平台下分析死锁问题的方法,对一些死锁问题的分析有一定作用。希望对大家有帮助。理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源 , 在系统运行过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统可能发生死锁,则不予分配,否则予以分配。因此,对资源的分配要给予合理的规划,使用有序资源分配法和银行家算法等是避免死锁的有效方法。
转自:http://www.ibm.com/developerworks/cn/linux/l-cn-deadlock/
【转】一个 Linux 上分析死锁的简单方法的更多相关文章
- 一个 Linux 上分析死锁的简单方法
简介 死锁 (deallocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这 ...
- PHP实现多文件上传的一些简单方法
下面我们就通过具体的代码示例,为大家介绍PHP实现多文件上传的一些简单方法. 第一种方法:利用单个文件上传方法 一段简单的form表单代码如下: <!DOCTYPE html> <h ...
- redis在Linux上的安装和简单使用
一.官方文档介绍方式 这里演示的版本是Redis4.0.6,Linux系统是CentOS6.7,Jdk1.7,Jedis2.8.1 下载,解压,编译: $ wget http://download.r ...
- 掌握这10种方法帮你快速在Linux上分析二进制文件
我们每天都使用二进制文件,但对二进制文件知之甚少.二进制是指您每天运行的可执行文件,从命令行工具到成熟的应用程序.Linux提供了丰富的工具集,可轻松进行二进制分析!无论您的工作角色是什么,如果您在L ...
- Linux上SQL及MYSQL简单操作
Linux上检查MYSQL是否安装: $ sudo service mysql start Ubuntu Linux安装配置MYSQL: $ sudo apt-get install mysql-se ...
- linux上wps2019不能启动解决方法
本人linux上的wps2016升级成wps2019后启动不了,双击图标没反应,在命令行输入wps,提示"/lib64/libc.so.6: version `GLIBC_2.18' not ...
- linux上安装Docker(非常简单的安装方法)
Docker的三大核心概念:镜像.容器.仓库 镜像:类似虚拟机的镜像.用俗话说就是安装文件. 容器:类似一个轻量级的沙箱,容器是从镜像创建应用运行实例, 可以将其启动.开始.停止.删除.而这些容器都是 ...
- linux上安装Docker (非常简单的安装方法) 2019
Docker的三大核心概念:镜像.容器.仓库 镜像:类似虚拟机的镜像.用俗话说就是安装文件. 容器:类似一个轻量级的沙箱,容器是从镜像创建应用运行实例, 可以将其启动.开始.停止.删除.而这些容器都是 ...
- Linux上Redis安装和简单操作
Redis redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorte ...
随机推荐
- Disconnected from the target VM, address: '127.0.0.1:57178', transport: 'socket'
idea 执行测试单元debug时控制台出现:Disconnected from the target VM, address: '127.0.0.1:57178', transport: 'sock ...
- Tesseract--主要API功能介绍
tesseract本身代码是由c/c++混编而成的,其中有用的简单的接口函数几乎都是在baseapi.h中. 从其处理过程中,不难得出:它还需要有一个image处理的类,及相关的方法:这样子, ...
- vue-cli3 一直运行 /sockjs-node/info?t= 解决方案
sockjs-node 是一个JavaScript库,提供跨浏览器JavaScript的API,创建了一个低延迟.全双工的浏览器和web服务器之间通信通道. 服务端:sockjs-node(https ...
- [Docker] 写 Dockerfile 的最佳实践理论
指导方针 创建短暂的容器 意思是 container 可以停止和销毁,接着以最小化启动和配置进行重新构建和替换. 理解构建的上下文 使用 docker build ,当前工作环境称 ...
- 网络yum源制作
思路:在网络上准备一个主机,在其中安装一个web服务器软件(比如apache),然后创建一个目录,在这个目录中准备上yum仓库的全部资源,如果用户可以通过网络访问到该主机的这个目录,那么这个目录就成为 ...
- 20175213 2018-2019-2 《Java程序设计》第7周学习总结
教材学习内容总结 (1)String (char a[])用一个字符数组a创建一个String对象. (2)String(char a[],int startIndex,int count) 提取字符 ...
- springBoot 全局异常方式处理自定义异常 @RestControllerAdvice + @ExceptionHandler
前言 本文讲解使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,可以处理大部分开发中用到的自自定义业务异常处理了,再也不用 ...
- Swagger2 header 添加token
@Bean public Docket apiDocument() { return new Docket(DocumentationType.SWAGGER_2) .groupName(" ...
- string转可空类型
string类型转可空类型 string.IsNullOrEmpty(str) ? (int?)null : int.Parse(str) 此处要注意将null转为可空的类型才可以,不然会报错
- Tomcat 配置MySQL连接池
<!--配置mysql数据库的连接池, 需要做的额外步骤是将mysql的Java驱动类放到tomcat的lib目录下 maxIdle 连接池中最多可 ...