首页 > 系统相关 >linux系统如何查看是否是线程死锁,多线程中如何使用gdb精确定位死锁问题

linux系统如何查看是否是线程死锁,多线程中如何使用gdb精确定位死锁问题

时间:2023-03-15 17:13:21浏览次数:34  
标签:std __ lock 死锁 线程 mutex 多线程

在多线程开发过程中很多人应该都会遇到死锁问题,死锁问题也是面试过程中经常被问到的问题,这里介绍在c++中如何使用gdb+python脚本调试死锁问题,以及如何在程序运行过程中检测死锁。

首先介绍什么是死锁,看下维基百科中的定义:

死锁(英语:Deadlock),又译为死结,计算机科学名词。当两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,就称为死锁。在多任务操作系统中,操作系统为了协调不同行程,能否获取系统资源时,为了让系统运作,必须要解决这个问题。

维基百科中介绍的是进程死锁,多线程中也会产生死锁,一样的道理,这里不作过多介绍。

死锁的四个条件

禁止抢占(no preemption):系统资源不能被强制从一个进程(线程)中退出,已经获得的资源在未使用完之前不能被抢占。

等待和保持(hold and wait):一个进程(线程)因请求资源阻塞时,对已获得的资源保持不放。

互斥(mutual exclusion):资源只能同时分配给一个进程(线程),无法多个进程(线程)共享。

循环等待(circular waiting):一系列进程(线程)互相持有其他进程(线程)所需要的资源。

只有同时满足以上四个条件,才会产生死锁,想要消除死锁只需要破坏其中任意一个条件即可。

如何调试多线程死锁问题

多线程出现死锁的大部分原因都是因为多个线程中加锁的顺序不一致导致的,看如下这段会出现死锁的代码:

using std::cout;

std::mutex mutex1;

std::mutex mutex2;

std::mutex mutex3;

void FuncA() {

std::lock_guard<:mutex> guard1(mutex1);

std::this_thread::sleep_for(std::chrono::seconds(1));

std::lock_guard<:mutex> guard2(mutex2);

std::this_thread::sleep_for(std::chrono::seconds(1));

}

void FuncB() {

std::lock_guard<:mutex> guard2(mutex2);

std::this_thread::sleep_for(std::chrono::seconds(1));

std::lock_guard<:mutex> guard3(mutex3);

std::this_thread::sleep_for(std::chrono::seconds(1));

}

void FuncC() {

std::lock_guard<:mutex> guard3(mutex3);

std::this_thread::sleep_for(std::chrono::seconds(1));

std::lock_guard<:mutex> guard1(mutex1);

std::this_thread::sleep_for(std::chrono::seconds(1));

}

intmain() {

std::thread A(FuncA);

std::thread B(FuncB);

std::thread C(FuncC);

std::this_thread::sleep_for(std::chrono::seconds(5));

if (A.joinable()) {

A.join();

}

if (B.joinable()) {

B.join();

}

if (C.joinable()) {

C.join();

}

cout <

return0;

}

如图:

线程A已经持有mutex1,想要申请mutex2,拿到mutex2后才可以释放mutex1和mutex2,而此时mutex2被线程B占用。

线程B已经持有mutex2,想要申请mutex3,拿到mutex3后才可以释放mutex2和mutex3,而此时mutex3被线程C占用。

线程C已经持有mutex3,想要申请mutex1,拿到mutex1后才可以释放mutex3和mutex1,而此时mutex1被线程A占用。

三个线程谁也不让着谁,导致了死锁。

传统gdb调试多线程死锁方法

(1)attach id关联到发生死锁的进程id

(gdb) attach 109

Attaching toprocess 109

[New LWP 110]

[New LWP 111]

[New LWP 112]

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

0x00007fa33f9e8d2d in__GI___pthread_timedjoin_ex (threadid=140339109693184, thread_return=0x0, abstime=0x0,

block=)atpthread_join_common.c:89

89      pthread_join_common.c: Nosuch fileordirectory.

(2)info threads查看当前进程中所有线程的信息,也可以查看到部分堆栈信息

(gdb) info threads

Id   Target Id         Frame

* 1    Thread 0x7fa33ff10740 (LWP 109) "out"0x00007fa33f9e8d2din__GI___pthread_timedjoin_ex (

threadid=140339109693184, thread_return=0x0, abstime=0x0, block=)atpthread_join_common.c:89

2    Thread 0x7fa33ec80700 (LWP 110) "out"__lll_lock_wait ()at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

3    Thread 0x7fa33e470700 (LWP 111) "out"__lll_lock_wait ()at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

4    Thread 0x7fa33dc60700 (LWP 112) "out"__lll_lock_wait ()at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

这里可以看到2、3、4线程都在lock_wait状态,基本上可以看出或许是否问题,但是不一定,这里需要多次info threads看看这些线程有没有什么变化,多次如果都没有变化那基本上就是发生了死锁。

(3)thread id进入具体线程

(gdb) thread 2

[Switching tothread 2 (Thread 0x7fa33ec80700 (LWP 110))]

#0 __lll_lock_wait () at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135     ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: Nosuch fileordirectory.

(4)bt查看当前线程堆栈信息

(gdb) bt

#0 __lll_lock_wait () at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

#1 0x00007fa33f9ea023 in__GI___pthread_mutex_lock (mutex=0x7fa340204180 )at../nptl/pthread_mutex_lock.c:78

#2 0x00007fa340000fff in__gthread_mutex_lock(pthread_mutex_t*) ()

#3 0x00007fa3400015b2 instd::mutex::lock() ()

#4 0x00007fa3400016d8 instd::lock_guard<:mutex>::lock_guard(std::mutex&) ()

#5 0x00007fa34000109b inFuncA() ()

#6 0x00007fa340001c07 invoid std::__invoke_impl(std::__invoke_other, void (*&&)()) ()

调试到这里基本已经差不多了,针对pthread_mutex_t却可以打印出被哪个线程持有,之后再重复步骤3和4,就可以确定哪几个线程以及哪几把锁发生的死锁,而针对于std::mutex,gdb没法打印具体的mutex的信息,不能看出来mutex是被哪个线程持有,只能依次进入线程查看堆栈信息。

然而针对于c++11的std::mutex有没有什么好办法定位死锁呢?

有。

可以算作第五步,继续:

(5)source加载deadlock.py脚本

(gdb) source -v deadlock.py

Type "deadlock"todetect deadlocks.

(6)输入deadlock检测死锁

(gdb) deadlock

[Switching tothread 3 (Thread 0x7f5585670700 (LWP 123))]

#0 __lll_lock_wait () at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135     in../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S

[Switching tothread 4 (Thread 0x7f5584e60700 (LWP 124))]

#0 __lll_lock_wait () at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135     in../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S

[Switching tothread 2 (Thread 0x7f5585e80700 (LWP 122))]

#0 __lll_lock_wait () at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135     in../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S

#1 0x00007f5586bea023 in__GI___pthread_mutex_lock (mutex=0x7f5587404180 )at../nptl/pthread_mutex_lock.c:78

[Switching tothread 3 (Thread 0x7f5585670700 (LWP 123))]

#0 __lll_lock_wait () at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

#1 0x00007f5586bea023 in__GI___pthread_mutex_lock (mutex=0x7f55874041c0 )at../nptl/pthread_mutex_lock.c:78

[Switching tothread 4 (Thread 0x7f5584e60700 (LWP 124))]

#0 __lll_lock_wait () at../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

#1 0x00007f5586bea023 in__GI___pthread_mutex_lock (mutex=0x7f5587404140 )at../nptl/pthread_mutex_lock.c:78

Found deadlock!

Thread 2 (LWP 122) iswaitingonpthread_mutex_t (0x00007f5587404180) heldbyThread 3 (LWP 123)

Thread 3 (LWP 123) iswaitingonpthread_mutex_t (0x00007f55874041c0) heldbyThread 4 (LWP 124)

Thread 4 (LWP 124) iswaitingonpthread_mutex_t (0x00007f5587404140) heldbyThread 2 (LWP 122)

直接看结果,脚本检测出了死锁,并指明了具体的哪几个线程造成的死锁,根据输出信息可以明显看出来线程锁形成的环造成了死锁,找到了具体是哪几个线程构成的死锁环,就可以查看相应线程的堆栈信息查看到哪几把锁正在等待。

死锁检测脚本的原理:

还是拿上面图举例:

线程A已经持有mutex1,想要申请mutex2,拿到mutex2后才可以释放mutex1和mutex2,而此时mutex2被线程B占用。

线程B已经持有mutex2,想要申请mutex3,拿到mutex3后才可以释放mutex2和mutex3,而此时mutex3被线程C占用。

线程C已经持有mutex3,想要申请mutex1,拿到mutex1后才可以释放mutex3和mutex1,而此时mutex1被线程A占用。

如图,三个线程形成了一个环,死锁检测就是检查线程之间是否有环的存在。单独检查死锁的环比较容易,这里延申下还涉及到简单环的概念,因为正常检测出来的环可能是个大环,不是权值顶点数最少的环,如果检测的环的顶点数较多,加大定位的代价,脚本就是检测的简单环,这里涉及到强连通分量算法和简单环算法,比较繁琐就不过多介绍了,脚本来源于facebook的folly库(这里推荐看下google的abseil和facebook的folly,都是好东西),代码较长在文中不好列出,如果有需要的话可以自行下载或者关注加我好友发给你。

如何在代码中检测死锁

和上面介绍的原理相同,在线程加锁过程中始终维护一张图,记录线程之间的关系

A->B, B->C, C->A

 

标签:std,__,lock,死锁,线程,mutex,多线程
From: https://www.cnblogs.com/lidabo/p/17219192.html

相关文章

  • Python多线程的坑——切换工作目录导致错误
    Python多线程的坑——切换工作目录导致错误复现:importosimporttimefromconcurrent.futuresimportThreadPoolExecutordefthread_func(a):origin=os.get......
  • Linux系统中多线程实现方法的全面解析
    ​线程引入:     在传统的Unix模型中,当一个进程需要由另一个实体执行某件事时,该进程派生(fork)一个子进程,让子进程去进行处理。Unix下的大多数网络服务器程序都是这么......
  • JVM线程运行诊断
    情况1CPU占用过多使用jstack命令如以下代码:/***测试类**/publicclassA{publicstaticvoidmain(String[]args)throwsInterruptedExceptio......
  • Day 16 16.4 案例分析之对比单线程与多线程
    线程案例:爬取斗图吧表情包图片方案一:单线程版本耗时慢importrequestsfromfake_useragentimportUserAgentimportrandomfromlxmlimportetreeimportosimpo......
  • ArkUI中的线程和看门狗机制
     一、前言本文主要分析ArkUI中涉及的线程和看门狗机制。 二、ArkUI中的线程应用Ability首次创建界面的流程大致如下: 说明:•AceContainer是一个容器类,由前端......
  • Java线程池和Spring异步处理高级篇
    开发过程中我们会遇到很多使用线程池的场景,例如异步短信通知,异步发邮件,异步记录操作日志,异步处理批量Excel解析。这些异步处理的场景我们都可以把它放在线程池中去完成,当然......
  • python入门学习-3.多线程、多进程、网络通信
    进程和线程多任务线程是最小的执行单元,而进程由至少一个线程组成。多进程Linux操作系统提供了一个fork()系统调用,子进程返回0,父进程返回子进程的ID。调用getpid()可以......
  • 多线程
    多线程1.创建线程方式创建线程方式一:继承Thread类,重写run()方法,调用start开启线程总结:线程开启不一定立即执行,有cpu调度执行packagecom.zhang.linePro;publicclassTe......
  • c++11多线程入门<学习记录>
    最近学习了c++多线程相关知识,也算是对这方面内容的入门视频链接c++11并发与多线程视频课程看了大概两周,简单进行总结参考文章C++11并发与多线程PS:c++11提供了标准的可......
  • 线程的实现方式
    用户级方式LUT(多对1):整个线程的信息都在用户空间 切換又进程管理,进程内部完成,內核根本不知道线程的切换 內核級方式KLT(1对1):线程控制块在內核空間,程序段,数据段在用户空......