并发编程 - 死锁的产生、排查与解决方案

news2024/12/27 10:47:24

在多线程编程中,死锁是一种非常常见的问题,稍不留神可能就会产生死锁,今天就和大家分享死锁产生的原因,如何排查,以及解决办法。

线程死锁通常是因为两个或两个以上线程在资源争夺中,形成循环等待,导致它们都无法继续执行各自后续操作的现象。

我们结合下图简单举个例子,线程1拥有资源A同时使用锁A进行锁定,并等待获取资源B;与此同时线程2拥有资源B同时使用锁B进行锁定,并等待获取资源A。此时便形成了线程1和线程2相互等待对方先释放锁的现象,形成了死循环,最终导致死锁。

在这里插入图片描述

01、产生死锁的必要条件

根据死锁产生的原因,可以总结出以下四个死锁产生的必要条件。

1、互斥条件

互斥即非此即彼,一个资源要不是我拥有,要不是你拥有,就是不能我们俩同时拥有。也就是互斥条件是指至少有一个资源处于非共享状态,一次只能有一个线程可以访问该资源。

2、占有并等待条件

该条件是指一个线程在拥有至少一个资源的同时还在等待获取其他线程拥有的资源。

3、不可剥夺条件

该条件是指一个线程一旦获取了某个资源,则不可被强行剥夺对该资源的所有权,只能等待该线程自己主动释放。

4、循环等待条件

循环等待是指线程等待资源形成的循环链,比如线程A等待资源B,线程B等待资源C,线程C等待资源A,但是资源A被线程A拥有,资源B被线程B拥有,资源C被线程C拥有,如此形成了依赖死循环,都在等待其他线程释放资源。

02、代码示例

下面我们实现一个简单的死锁代码示例,代码如下:

//锁1
private static readonly object lock1 = new();
//锁2
private static readonly object lock2 = new();
//模拟两个线程死锁
public static void ThreadDeadLock()
{
    //线程1
    var thread1 = new Thread(Thread1);
    //线程2
    var thread2 = new Thread(Thread2);
    //线程1 启动
    thread1.Start();
    //线程2 启动
    thread2.Start();
    //等待 线程1 执行完毕
    thread1.Join();
    //等待 线程2 执行完毕
    thread2.Join();
}
//线程1
public static void Thread1()
{
    //线程1 首先获取 锁1
    lock (lock1)
    {
        Console.WriteLine("线程1: 已获取 锁1");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程1: 等待获取 锁2");
        //线程1 等待 锁2
        lock (lock2)
        {
            Console.WriteLine("线程1: 已获取 锁2");
        }
    }
}
//线程2
public static void Thread2()
{
    //线程2 首先获取 锁2
    lock (lock2)
    {
        Console.WriteLine("线程2: 已获取 锁2");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程2: 等待获取 锁1");
        //线程2 等待 锁1
        lock (lock1)
        {
            Console.WriteLine("线程2: 已获取 锁1");
        }
    }
}

在上面的代码中,thread1 先拥有lock1,然后尝试获取lock2;thread2 先拥有锁住 lock2,然后尝试获取lock1;由于线程间相互等待对方释放资源,所以导致死锁。

下面我们看看上面代码执行效果:

在这里插入图片描述

可以发现线程1和线程2都在等待彼此所拥有的锁。

03、排查死锁

上一节中我们编写了一个简单的死锁代码示例,但是实际研发过程中代码不可能这么简单直观,一眼就能看出来问题所在。因此如何排查发生死锁呢?

其实我们的开发工具Visual Studio就可以查看。可以通过调试菜单中窗口下的线程、调用堆栈、并行堆栈等调试窗口查看。

上面代码正常运行后,编辑器为如下状态,也没有报错,啥也看不出来。

在这里插入图片描述

在默认状态下是无法看出东西,此时我们只需要点击全部中断按钮,则死锁的相关信息都会展示出来,如下图。

在这里插入图片描述

可以看到已经提示检测到死锁了,同时在调用堆栈窗口中还可以通过双击切换具体发生死锁的代码。

我们再切换至并行堆栈调试窗口,和调用堆栈相比,并行堆栈窗口更偏向图形化,并且发生死锁的两个线程方法都有体现出来,同样可以通过双击切换到具体代码,如下图:

在这里插入图片描述

下面我们再来看看线程调试窗口,如下图,可以发现前面有两个箭头,其中黄色箭头表示当前选中的发生死锁的代码,图中绿色选中代码,灰色箭头表示第一个发生死锁的代码。可以通过双击当前窗口中行进行发生死锁代码的切换,如下图:

在这里插入图片描述

当然还可以通过其他方式排查死锁,比如分析dump文件,这里就不深入了,后面有机会再单独讲解。

04、解决办法

下面介绍几种避免死锁的指导思想。

1、顺序加锁

顺序加锁就是为了避免产生循环等待,如果大家都是先锁定lock1,再锁定lock2,则就不会产生循环等待。

看看如下代码:

//线程1
public static void Thread1New()
{
    //线程1 首先获取 锁1
    lock (lock1)
    {
        Console.WriteLine("线程1: 已获取 锁1");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程1: 等待获取 锁2");
        //线程1 等待 锁2
        lock (lock2)
        {
            Console.WriteLine("线程1: 已获取 锁2");
        }
    }
}
//线程2
public static void Thread2New()
{
    //线程2 首先获取 锁2
    lock (lock1)
    {
        Console.WriteLine("线程2: 已获取 锁2");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程2: 等待获取 锁1");
        //线程2 等待 锁1
        lock (lock2)
        {
            Console.WriteLine("线程2: 已获取 锁1");
        }
    }
}

我们看看代码执行结果。

在这里插入图片描述

2、使用尝试锁

我们可以使用一些其他锁机制,比如使用Monitor.TryEnter方法尝试获取锁,如果在指定时间内没有获取到锁,则释放当前所拥有的锁,以此来避免死锁。

3、使用超时机制

我们可以通过Thead结合CancellationToken实现超时机制,避免线程无限等待。当然可以直接使用Task,因为Task本身就支持CancellationToken,提供了内置的取消支持使用起来更方便。

4、避免嵌套使用锁

一个线程在拥有一个锁的同时尽量避免再去申请另一个锁,这样可以避免循环等待。

上面是使用Thread实现的示例,现在大家直接使用Thread可能比较少,大多数都是使用Task,最后给大家一个Task死锁示例,代码如下:

//锁1
private static readonly object lock1 = new();
//锁2
private static readonly object lock2 = new();
//模拟两个任务死锁
public static async Task TaskDeadLock()
{
    //启动 任务1
    var task1 = Task.Run(() => Task1());
    //启动 任务2
    var task2 = Task.Run(() => Task2());
    //等待两个任务完成
    await Task.WhenAll(task1, task2);
}
//任务1
public static async Task Task1()
{
    //任务1 首先获取 锁1
    lock (lock1)
    {
        Console.WriteLine("任务1: 已获取 锁1");
        //模拟一些操作
        Task.Delay(1000).Wait();
        //任务1 等待 锁2
        Console.WriteLine("任务1: 等待获取 锁2");
        lock (lock2)
        {
            Console.WriteLine("任务1: 已获取 锁2");
        }
    }
}
//任务2
public static async Task Task2()
{
    //线程2 首先获取 锁2
    lock (lock2)
    {
        Console.WriteLine("任务2: 已获取 锁2");
        //模拟一些操作
        Task.Delay(100).Wait();
        // 任务2 等待 锁1
        Console.WriteLine("任务2: 等待获取 锁1");
        lock (lock1)
        {
            Console.WriteLine("任务2: 获取 锁1");
        }
    }
}

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2266340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于埃斯顿机器人文件导出或者系统日志导出

关于埃斯顿机器人文件导出或者日志导出,登录模式,选择高级设置,控制器备份恢复 选择U盘导入地址,点击导出,等待时间30秒就可以查看文件格式和系统日志

5G CPE接口扩展之轻量型多口千兆路由器小板选型

多口千兆路由器小板选型 方案一: 集成式5口千兆WIFI路由器小板方案二:交换板 + USBwifiUSB WIFI选型一USBwifi选型二:四口千兆选型一四口千兆选型二:四口千兆选型三:部分5G CPE主板不支持Wifi,并且网口数量较少,可采用堆叠方式进行网口和wifi功能 扩展,本文推荐一些路由…

PromptGIP:Unifying lmage Processing as Visual Prompting Question Answering

“Unifying Image Processing as Visual Prompting Question Answering” 文章提出了一种名为 PromptGIP 的通用模型,将图像处理任务统一为视觉提示问答范式,在多个图像处理任务上展现出良好性能,为通用图像处理提供了新的思路和方法。 confe…

【MySQL】索引 面试题

文章目录 适合创建索引的情况创建索引的注意事项MySQL中不适合创建索引的情况索引失效的常见情况 索引定义与作用 索引是帮助MySQL高效获取数据的有序数据结构,通过维护特定查找算法的数据结构(如B树),以某种方式引用数据&#xf…

Doxygen 使用指南

Doxygen 是一个文档生成工具,可以从源代码中的注释生成高质量的文档,支持多种编程语言(如 C/C、Python、Java 等)。以下是 Doxygen 的基本使用方法。 1. 安装 Doxygen 1.1 下载 Doxygen 访问 Doxygen 官网。根据操作系统选择合适…

Jensen-Shannon Divergence:定义、性质与应用

一、定义 Jensen-Shannon Divergence(JS散度)是一种衡量两个概率分布之间差异的方法,它是Kullback-Leibler Divergence(KL散度)的一种对称形式。JS散度在信息论、机器学习和统计学等领域中具有广泛的应用。 给定两个概…

一个特别的串口通讯

背景 设备是EPICS流式细胞仪,这个设备的控制系统是在DOS系统上的。数据存储在硬盘上,不带串口通讯功能。我们遇到了这个设备后,就开发了一个DOS下的执行程序通过串口,将最新的数据自动上传到服务器上。 编译工具 Turbo C 数据…

4.系统学习-集成学习

集成学习 前言Bias and Variance过拟合(overfitting)与欠拟合(underfitting)集成学习为什么有效?Blending 模型集成Stakcing 模型集成Bagging模型集成Bagging 模型集成算法流程:Boosting模型集成作业 前言 …

电商项目高级篇07-redisson分布式锁

redisson分布式锁 1、引入maven依赖2、config类3、可重入锁设计 1、引入maven依赖 <!--引入redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.0</version></depend…

Nginx的性能分析与调优简介

Nginx的性能分析与调优简介 一、Nginx的用途二、Nginx负载均衡策略介绍与调优三、其他调优方式简介四、Nginx的性能监控 一、Nginx的用途 ‌Nginx是一种高性能的HTTP和反向代理服务器&#xff0c;最初作为HTTP服务器开发&#xff0c;主要用于服务静态内容如HTML文件、图像、视…

递归算法常见问题(Java)

问题&#xff1a;斐波那契数列,第1项和第2项都为1&#xff0c;后面每一项都为相邻的前俩项的和,求第n个数 解法&#xff1a;每一个数都为前俩个数之和&#xff0c;第1项和第2项都为1&#xff0c;所以写 方法f1(n)即为求第n个数&#xff0c;那么f1(n-1)为求第n-1个数&#xff0…

SpringBoot + HttpSession 自定义生成sessionId

SpringBoot HttpSession 自定义生成sessionId 业务场景实现方案 业务场景 最近在做用户登录过程中&#xff0c;由于默认ID是通过UUID创建的&#xff0c;缺乏足够的安全性&#xff0c;决定要自定义生成 sessionId。 实现方案 正常的获取session方法如下&#xff1a; HttpSe…

【流量、洪水数据下载】网站介绍和下载经验....不断更新!

EarthData 介绍 NASA:链接 数据下载 视频教程:Discover and Access Earth Science Data Using Earthdata Search 数据下载需要注册,已经被卡在邮箱验证上【2024-12-26】… Essential Climate Variables——【The Global Climate Observing System(GCOS) 介绍 其是支…

Pytorch | 利用SMI-FGRM针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用I-FGSSM针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集SMI-FGRM介绍SMI-FGRM算法流程 SMI-FGRM代码实现SMI-FGRM算法实现攻击效果 代码汇总smifgrm.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器&#xff1a; Pytorch | 从零构建AlexNet对CI…

少儿编程在线培训系统:客户服务与学习支持

2.1 VUE技术 VUE它是由HTML代码&#xff0c;配上嵌入在HTML代码里面的Java代码组成的应用于服务器端的语言&#xff0c;使用VUE进行开发能够更加容易区分网页逻辑以及网页设计内容&#xff0c;让程序员开发思路更加清晰化&#xff0c;VUE在设计组件时&#xff0c;它是可以重用的…

流批一体向量化计算引擎 Flex 在蚂蚁的探索和实践

编者按&#xff1a;Flex是蚂蚁数据部自研的一款流批一体的向量化引擎&#xff0c;Flex是Fink和Velox的全称&#xff0c;也是Flexible的前缀&#xff0c;被赋予了灵活可插拔的寓意。本文将重点从向量化技术背景、Flex架构方案和未来规划三个方面展开论述。 作者介绍&#xff1a;…

upload-labs关卡记录11

先上传一个一句话木马试试&#xff0c;居然可以上传成功&#xff0c;复制图片链接&#xff0c;在另一个窗口打开&#xff1a; 会发现&#xff0c;我们明明上传的是shell.php&#xff0c;但是这里就是没有了php,这样我们在执行我们相关的语句的时候就无法执行了&#xff1a; 就…

WebRTC服务质量(08)- 重传机制(05) RTX机制

WebRTC服务质量&#xff08;01&#xff09;- Qos概述 WebRTC服务质量&#xff08;02&#xff09;- RTP协议 WebRTC服务质量&#xff08;03&#xff09;- RTCP协议 WebRTC服务质量&#xff08;04&#xff09;- 重传机制&#xff08;01) RTX NACK概述 WebRTC服务质量&#xff08;…

借助 obdiag,让 OceanBase 参数和变量的对比更简单

本文将介绍 obdiag 工具中参数对比和变量对比功能的适用场景和试用方法。​​​​​​​ obdiag 参数和变量对比功能的适用场景 参数对比功能适用场景 不同observer对于同一参数允许配置不同的值&#xff0c;实际生产环境中&#xff0c;用户可能因多种原因在不同observer上为同…

Net9解决Spire.Pdf替换文字后,文件格式乱掉解决方法

官方文档 https://www.e-iceblue.com/Tutorials/Spire.PDF/Program-Guide/Text/Find-and-replace-text-on-PDF-document-in-C.html C# 在 PDF 中查找替换文本 原文件如下图&#xff0c;替换第一行的新编码&#xff0c;把41230441044替换为41230441000 替换代码如下&#xff…