您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧

时间:2023-11-23 12:20:36  来源:微信公众号  作者:囧囧妹

多线程编程是现代软件开发中的一项重要技术,但随之而来的挑战之一是多线程死锁。多线程死锁是程序中的一种常见问题,它会导致线程相互等待,陷入无法继续执行的状态。这里,我们将探讨多线程死锁的概念、原理,同时我们通过一个例子来介绍如何使用GDB(GNU Debugger)这一工具来排查和解决多线程死锁问题。

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧

多线程死锁的概念

 

多线程死锁是多线程编程中的一种关键问题。它发生在多个线程试图获取一组资源(通常是锁或资源对象)时,导致彼此相互等待的情况。具体来说,当线程1持有资源A并等待资源B,而线程2持有资源B并等待资源A时,就可能发生死锁。

多线程死锁原理

 

为了更好地理解多线程死锁的原理,让我们考虑一个简单的示例。假设有两个资源A和B,以及两个线程(Thread 1和Thread 2)。线程1需要获取资源A和B,线程2需要获取资源B和A。如果线程1获取了资源A,而线程2获取了资源B,它们都无法继续,因为它们都需要对方持有的资源才能继续。这就是典型的死锁情况。

 

多线程死锁通常发生在以下情况下:

  • 线程同时持有一个资源并等待另一个资源。
  • 资源分配不当,线程没有按照相同的顺序获取资源。

多线程死锁之所以会发生,是因为线程之间的相互依赖和等待。当多个线程需要共享资源时,它们可能会按不同的顺序获取这些资源,导致资源互斥问题,最终引发死锁。

排查多线程死锁

GDB是一个强大的调试工具,可以用来排查多线程死锁问题。下面通过一个例子来说下如何使用gdb调试死锁问题,这也是前段时间我碰锁问题新学到的技能。

简单的代码如下:


#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t exit_condition = PTHREAD_COND_INITIALIZER;
int should_exit = 0;

void *thread1_function(void *arg) {
    while (1) {
        printf("Thread 1: Attempting to acquire mutex1...n");
        pthread_mutex_lock(&mutex1);
        printf("Thread 1: Acquired mutex1.n");

        printf("Thread 1: Attempting to acquire mutex2...n");
        pthread_mutex_lock(&mutex2);
        printf("Thread 1: Acquired mutex2.n");

        // 在此处检查是否应该退出
        if (should_exit) {
            pthread_mutex_unlock(&mutex2);
            pthread_mutex_unlock(&mutex1);
      break;
        }

        pthread_mutex_unlock(&mutex1);
        pthread_mutex_unlock(&mutex2);
    }
    printf("Thread 1 exit done!n");
    pthread_exit(NULL);
}

void *thread2_function(void *arg) {
    sleep(5); // 让线程2休眠10秒钟

    printf("Thread 2: Attempting to acquire mutex2...n");
    pthread_mutex_lock(&mutex2);
    printf("Thread 2: Acquired mutex2.n");

    printf("Thread 2: Notifying Thread 1 to exit...n");
    should_exit = 1;
    pthread_cond_signal(&exit_condition);

    //通过不释放该锁制造死锁
    pthread_mutex_unlock(&mutex2);

    printf("Thread 2 exit done!n");
    //exit执行后不会再执行该函数后面部分
    pthread_exit(NULL);
}

int mAIn() {
    pthread_t thread1, thread2;

    pthread_create(&thread1, NULL, thread1_function, NULL);
    pthread_create(&thread2, NULL, thread2_function, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

代码很简单,通过创建两个线程,线程1睡眠5s为mutex2加锁并通知线程1进行退出,之后线程2退出,线程1是个while循环,不停的对mutex1进行加解锁,并加锁后检测是否退出,退出则对mutex2进行加锁打印,然后释放mutex1、mutex2进行退出。

使用:gcc thread.c -g -lpthread -o thread编译,因为要gdb调试所以需要带上-g参数,正常现象会执行结束打印如下:

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧

现在我们屏蔽掉线程2释放mutex2进行死锁调试:

void *thread2_function(void *arg) {
    sleep(5); // 让线程2休眠10秒钟

    printf("Thread 2: Attempting to acquire mutex2...n");
    pthread_mutex_lock(&mutex2);
    printf("Thread 2: Acquired mutex2.n");

    printf("Thread 2: Notifying Thread 1 to exit...n");
    should_exit = 1;
    pthread_cond_signal(&exit_condition);

    //通过不释放该锁制造死锁
    //pthread_mutex_unlock(&mutex2);

    printf("Thread 2 exit done!n");
    //exit执行后不会再执行该函数后面部分
    pthread_exit(NULL);
}

实际环境中我们并不知道死锁发生,所以我们通过gdb先运行一次直到程序无法正常退出时,执行bt查看堆栈:

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧

这里因为加了打印所以很快可以看到mutex2上锁那里卡住,实际环境会有很多线程运行,我们并不直到哪里会有问题,此时只能通过bt查看堆栈我们发现卡在函数__futex_abstimed_wait_common64,运行到./nptl/futex-internal.c文件第57行。

这里我们只需要知道该函数__futex_abstimed_wait_common64是linux内核中用于处理互斥锁等待超时的一个内部函数即可。

此时可以断定代码存在死锁问题了,我们继续排查。

我们继续看bt信息,发现该等待是从#4  0x00005555555553c8 in main () at thread.c:59调入的,因为前面是#4,所以使用f 4进入该函数。

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧

 

我们发现是main里调入,同时在执行thread1的pthread_join,所以前面的__futex_abstimed_wait_common64并不是我们真正要找的问题,其实thread1已经来到了join的位置,等待结束了。我们继续执行thread Apply all bt把所有线程堆栈打出来看下:

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧

根据前面分析thread 1已经正常退出了,我们这里看到thread 2卡在futex_wait,根据上下文非常明显是在等待futex lock,再往下看我们发现锁mutex2,这里就是thread2在等待mutex2,那么mutex2被谁lock住没释放呢?我们通过p mutex2来查看owner即可知道该锁被谁拥有。

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧

这里有个问题,是因为该代码恰巧thread 1退出等待join了,所以这里的23890是个内核线程,在持有着mutex2,实际环境中我们会看到owner大概会是info threads中的LWP,于是就可以定位到该锁被谁持有没有释放了,再分析代码即可。

我把thread 1再改下,不直接退出而是一直while(1)的形态来测试,此时再通过上述来查找mutex2被谁持有即可直观看到:

解锁多线程死锁之谜:深入探讨使用GDB调试的技巧



Tags:死锁   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  Search: 死锁  点击:(47)  评论:(0)  加入收藏
MySQL事务中遇到死锁问题该如何解决?
在并发访问下,MySQL事务中的死锁问题是一种常见的情况。当多个事务同时请求和持有相互依赖的资源时,可能会出现死锁现象,导致事务无法继续执行,严重影响系统的性能和可用性。死...【详细内容】
2024-01-10  Search: 死锁  点击:(102)  评论:(0)  加入收藏
多个线程或进程竞争共享资源而导致的死锁问题
死锁是多线程或多进程并发编程中常见的问题之一,它会导致程序无法继续执行下去,造成系统资源的浪费和性能下降。在Java项目中,当多个线程或进程竞争共享资源时,如果不恰当地处理...【详细内容】
2023-12-07  Search: 死锁  点击:(149)  评论:(0)  加入收藏
解锁多线程死锁之谜:深入探讨使用GDB调试的技巧
多线程编程是现代软件开发中的一项重要技术,但随之而来的挑战之一是多线程死锁。多线程死锁是程序中的一种常见问题,它会导致线程相互等待,陷入无法继续执行的状态。这里,我们将...【详细内容】
2023-11-23  Search: 死锁  点击:(169)  评论:(0)  加入收藏
MySQL 事务死锁问题排查
一、背景 在预发环境中,由消息驱动最终触发执行事务来写库存,但是导致 MySQL 发生死锁,写库存失败。com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: rpc...【详细内容】
2023-09-27  Search: 死锁  点击:(376)  评论:(0)  加入收藏
从一个死锁问题分析优化器特性
作者:李锡超,一个爱笑的江苏苏宁银行 数据库工程师,主要负责数据库日常运维、自动化建设、DMP 平台运维。擅长 MySQL、Python、Oracle,爱好骑行、研究技术。爱可生开源社区出品...【详细内容】
2023-09-22  Search: 死锁  点击:(179)  评论:(0)  加入收藏
一个 MySQL 数据库死锁的案例和解决方案
本文介绍了一个 MySQL 数据库死锁的案例和解决方案。场景生产环境出了一个偶现的数据库死锁问题,导致少部分业务处理失败。分析特征之后,发现是多个线程并发执行同一个方法,更...【详细内容】
2023-09-01  Search: 死锁  点击:(285)  评论:(0)  加入收藏
Mybatis-Plus可能会导致数据库死锁
一、场景还原1.版本信息MySQL版本:5.6.36-82.1-logMybatis-Plus的starter版本:3.3.2存储引擎:InnoDB2.死锁现象A同学在生产环境使用了Mybatis-Plus提供的com.baomidou.mybatisp...【详细内容】
2023-08-14  Search: 死锁  点击:(171)  评论:(0)  加入收藏
Oracle 死锁与慢查询总结
查看死锁SELECT s.sid "会话ID",s.lockwait "等待锁",s.event "等待的资源/事件", -- 最近等待或正在等待的资源/事件DECODE(lo.locked_mode, 0, &#39;尚未获得锁&#39;, 1,...【详细内容】
2023-05-22  Search: 死锁  点击:(108)  评论:(0)  加入收藏
在 Linux 内核中调试 FUSE 死锁
Netflix 的计算团队负责管理 Netflix 的所有 AWS 和容器化工作负载,包括自动缩放、容器部署、问题修复等。作为该团队的一员,我致力于修复用户报告的奇怪问题。这个特殊问题...【详细内容】
2023-05-21  Search: 死锁  点击:(421)  评论:(0)  加入收藏
▌简易百科推荐
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(5)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(12)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(8)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(10)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(8)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  云原生散修  微信公众号  Tags:Kubernetes   点击:(5)  评论:(0)  加入收藏
站内最新
站内热门
站内头条