Linux线程间死锁分析
死锁 (deadlocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。 由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程(线程)在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
一种交叉持锁死锁的情形,此时执行程序中两个或多个线程发生永久堵塞(等待),每个线程都在等待被其它线程占用并堵塞了的资源。例如,如果线程 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 手册。
#include <unistd.h>
#include <pthread.h>
#include <string.h>
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;
}
执行编译命令
g++ -g lock.cpp -o lock -lpthread
-g选项必须有
gdb调试可执行文件定位死锁
a) gdb attach到目标程序
gdb attach 12814
b) 查看该进程内的线程信息
(gdb) info thread
Id Target Id Frame
* 1 Thread 0x7f77767ac700 (LWP 12814) "lock" 0x00007f777639998d in pthread_join (threadid=140151057311488, thread_return=0x0)
at pthread_join.c:90
2 Thread 0x7f7775fc6700 (LWP 12815) "lock" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
3 Thread 0x7f77757c5700 (LWP 12816) "lock" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
4 Thread 0x7f7774fc4700 (LWP 12817) "lock" 0x00007f777609330d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
5 Thread 0x7f77747c3700 (LWP 12818) "lock" 0x00007f777609330d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
(gdb)
c) 查看当前线程栈空间
(gdb) bt
#0 0x00007f777639998d in pthread_join (threadid=140151057311488, thread_return=0x0) at pthread_join.c:90
#1 0x0000000000400b86 in main () at lock.cpp:110
可以知道主线程在等待子线程结束
c) 切换到线程2,并查看其堆栈
(gdb) thread 2
[Switching to thread 2 (Thread 0x7f7775fc6700 (LWP 12815))]
#0 __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135 ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.
(gdb) bt
#0 __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1 0x00007f777639adbd in __GI___pthread_mutex_lock (mutex=0x6020e0 <mutex2>) at ../nptl/pthread_mutex_lock.c:80
#2 0x0000000000400907 in func1 () at lock.cpp:18
#3 0x000000000040099f in thread1 (arg=0x0) at lock.cpp:43
#4 0x00007f77763986ba in start_thread (arg=0x7f7775fc6700) at pthread_create.c:333
#5 0x00007f77760ce41d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
(gdb)
可以知道线程2在lock.cpp的第18行申请mutex2的互斥锁。并阻塞在这儿了,我们进一步看看这个互斥锁当前是被谁持有的。
(gdb) p mutex2
$1 = {__data = {__lock = 2, __count = 0, __owner = 12816, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0,
__next = 0x0}}, __size = "\002\000\000\000\000\000\000\000\020\062\000\000\001", '\000' <repeats 26 times>, __align = 2}
(gdb)
从这儿我们可以知道,mutex2这个锁之前已经被线程12816(线程3)持有了,我们切换到这个线程去看看。
(gdb) thread 3
[Switching to thread 3 (Thread 0x7f77757c5700 (LWP 12816))]
#0 __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135 in ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S
(gdb) bt
#0 __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1 0x00007f777639adbd in __GI___pthread_mutex_lock (mutex=0x6020a0 <mutex1>) at ../nptl/pthread_mutex_lock.c:80
#2 0x0000000000400963 in func2 () at lock.cpp:31
#3 0x00000000004009c6 in thread2 (arg=0x0) at lock.cpp:56
#4 0x00007f77763986ba in start_thread (arg=0x7f77757c5700) at pthread_create.c:333
#5 0x00007f77760ce41d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
(gdb)
我们发现线程3在lock.cpp的第31行尝试申请mutex1的互斥锁,并陷入了阻塞。同理我们看看这个互斥锁的ower是谁
(gdb) p mutex1
$2 = {__data = {__lock = 2, __count = 0, __owner = 12815, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0,
__next = 0x0}}, __size = "\002\000\000\000\000\000\000\000\017\062\000\000\001", '\000' <repeats 26 times>, __align = 2}
(gdb)
OK,mutex1这个互斥锁被12815(线程2)持有了。
因此我们可以得到结论了,线程2在等待线程3释放锁资源,于此同时,线程3也在等待线程2释放锁资源,从而进程出现卡死现象。