我是靠谱客的博主 可耐过客,最近开发中收集的这篇文章主要介绍Linux下排除死锁详细教程(基于C++11、GDB)Linux下排除死锁详细教程(基于C++11、GDB)1. 前言2. 模拟死锁3. 排查死锁,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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. 排查死锁所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部