概述
Linux下排除死锁详细教程(基于C++11、GDB)
文章目录
- Linux下排除死锁详细教程(基于C++11、GDB)
- 1. 前言
- 2. 模拟死锁
- 3. 排查死锁
1. 前言
在实际编写项目的过程中,经常涉及到多线程。多线程的程序编程很大概率会涉及到线程安全的问题,因此往往使用互斥锁来保证线程安全。
然而,互斥锁的使用却经常会导致另外一个问题:死锁。
所谓死锁,通俗的讲就是有两个共享资源,一个在你手上,一个在我手上。我等着你用完把另一个给我,你等我用完给你,这样相互等待就形成了死锁。
所以,在这里,基于Linux的环境,使用C++11提供的多线程编程来模拟死锁,并尝试从“不知情”的角度,使用shell + gbd
进行死锁的排查。
2. 模拟死锁
1 #include <iostream>
2 #include <thread>
3 #include <mutex>
4
5 using namespace std;
6
7 mutex mtxA;
8 mutex mtxB;
9
10 void taskA() {
11 lock_guard<mutex> lockA(mtxA);
12 cout << "Thread A get Lock A!" << endl;
13 this_thread::sleep_for(chrono::seconds(1));
14 lock_guard<mutex> lockB(mtxB);
15 cout << "Thread A get Lock A and B!" << endl;
16 cout << "Thread A relese all source!" << endl;
17
18 }
19
20 void taskB() {
21 lock_guard<mutex> lockB(mtxB);
22 cout << "Thread B get Lock B!" << endl;
23 this_thread::sleep_for(chrono::seconds(1));
24 lock_guard<mutex> lockA(mtxA);
25 cout << "Thread B get Lock B and A!" << endl;
26 cout << "Thread B relese all source!" << endl;
27 }
28
29 int main() {
30
31 thread t1(taskA);
32 thread t2(taskB);
33
34 t1.join();
35 t2.join();
36
37 return 0;
38 }
这里我们使用了C++11中的多线程编程来模拟了一个死锁的场景。
首先,对于线程t1,会执行工作函数taskA。在taskA里面,线程t1首先会拿到 mtxA 这把互斥锁。然后睡眠1秒,让 线程t2执行工作函数taskB,由线程t2拿到 mtxB 这把互斥锁。这时,线程t1醒过来想拿锁B,而B却在t2手里。t2在拿到 mtxB 时,马上想获取 mtxA,但是在睡眠的t1手里。
因此,线程t1和t2循环等待对方的资源,又释放自己手里的资源,这样就形成了死锁。
我们将这个文件命名为:deadLock.cc
,并使用g++进行编译,生成可执行文件 (tip:注意一定要加上 -pthread,因为C++11里面的多线程库在Linux下也是基于pthread这个库实现的
)::
g++ deadLock.cc -pthread -o deadLock -g
如此生成了可执行文件,因此可以运行:
./deadLock
运行结果:
可以看见,线程t1和t2各自获取了一把锁,同时它们都在等待对面释放手里的锁,因此造成了死锁。
3. 排查死锁
首先,我们怀疑一个程序发生了死锁,首先可以查看该进程CPU利用率、内存利用率的情况。因为如果发生了死锁(这里假设是互斥锁),进程里面发生死锁的线程会处于阻塞的状态,此时基本不占有CPU,因此CPU的利用率、内存占有率将会比较低。 我们可以使用 ps aux 命令来拿到一个进程的状态:
ps aux | grep deadLock
这里可以看见有两个和deadLock相关的进程:6586、6674。后面一个是执行了grep这个命令之后产生的进程,所以第一个进程是我们想排查的进程。
然后,使用 top 命令来查看进程的CPU利用率、内存占有率:
top -Hp 6586
可以看见,这个进程里面一共存在三个线程。仔细思考,应该是对应线程t1、t2、和 main线程。它们的CPU利用率、内存都是0,很有可能发生了死锁。
此时,进程已经运行起来了。在实际的项目中,我们一般也不可能把一个进程停掉用GDB调试。因此,只能用GDB 的 attach 命令来跟踪这个进程:
首先需要超级权限,输入密码:
su
然后:
gdb attach 6586
再查看三个线程的堆栈的调用情况:
thread apply all bt
(按 enter键)
(按 enter键)
可以看见,上面的三张图显示了三个线程的堆栈的调用情况。我们仔细观察每个线程:
这里红色框中有main,说明线程1是主线程。
这里红色框中有taskA,说明线程2是线程t1。
这里红色框中有taskB,说明线程3是线程t2。
因此,到此,我们知道了线程1是main线程、线程2是t1、线程3是t2。所以,下一步我们单独查看每个线程的堆栈调用情况。 使用:
info threads
得到:
各个线程的索引。
使用 thread + 线程索来切换到某个线程:
thread 1
使用 bt 来查看堆栈当前线程的堆栈调用:
可以大概浏览一下,当前线程没有和锁相关的调用,所以大概率死锁不发生在这个线程中。
接下来切换到线程2,再使用bt 查看堆栈:
在上面这张图中,从上往下看,找到进程名+行数的组合最后出现的地方,出现在程序的14行。我们用vim看一下程序的14行是什么:
刚好是线程t1醒来后想去拿锁B的行数。所以基本可以判断,这个线程拿不到锁,一直阻塞等待。
我们再查看线程3的情况 :
可以看见,线程3在执行完进程的23行基本阻塞住了,我们再去看看23行是什么:
可以看见,线程t2想拿锁A,但锁A在t1手里,所以它们俩循环等待对方先释放锁,造成了死锁。
至此,排除基本结束。接下来解除死锁的办法有很多,比如使用资源有序分配法
,对于线程t1、t2按相同的顺序分配锁(这里线程t1先A再B,线程t2先B再A,因此死锁了),或者先把锁分配给一个线程,用完再释放全部的锁
,这样也能解决死锁的问题。
最后
以上就是可耐过客为你收集整理的Linux下排除死锁详细教程(基于C++11、GDB)Linux下排除死锁详细教程(基于C++11、GDB)1. 前言2. 模拟死锁3. 排查死锁的全部内容,希望文章能够帮你解决Linux下排除死锁详细教程(基于C++11、GDB)Linux下排除死锁详细教程(基于C++11、GDB)1. 前言2. 模拟死锁3. 排查死锁所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复