- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在多线程编程中,死锁是一种非常常见的问题,稍不留神可能就会产生死锁,今天就和大家分享死锁产生的原因,如何排查,以及解决办法.
线程死锁通常是因为两个或两个以上线程在资源争夺中,形成循环等待,导致它们都无法继续执行各自后续操作的现象.
我们结合下图简单举个例子,线程1拥有资源A同时使用锁A进行锁定,并等待获取资源B;与此同时线程2拥有资源B同时使用锁B进行锁定,并等待获取资源A。此时便形成了线程1和线程2相互等待对方先释放锁的现象,形成了死循环,最终导致死锁.
根据死锁产生的原因,可以总结出以下四个死锁产生的必要条件.
互斥即非此即彼,一个资源要不是我拥有,要不是你拥有,就是不能我们俩同时拥有。也就是互斥条件是指至少有一个资源处于非共享状态,一次只能有一个线程可以访问该资源.
该条件是指一个线程在拥有至少一个资源的同时还在等待获取其他线程拥有的资源.
该条件是指一个线程一旦获取了某个资源,则不可被强行剥夺对该资源的所有权,只能等待该线程自己主动释放.
循环等待是指线程等待资源形成的循环链,比如线程A等待资源B,线程B等待资源C,线程C等待资源A,但是资源A被线程A拥有,资源B被线程B拥有,资源C被线程C拥有,如此形成了依赖死循环,都在等待其他线程释放资源.
下面我们实现一个简单的死锁代码示例,代码如下:
//锁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都在等待彼此所拥有的锁.
上一节中我们编写了一个简单的死锁代码示例,但是实际研发过程中代码不可能这么简单直观,一眼就能看出来问题所在。因此如何排查发生死锁呢?
其实我们的开发工具Visual Studio就可以查看。可以通过调试菜单中窗口下的线程、调用堆栈、并行堆栈等调试窗口查看.
上面代码正常运行后,编辑器为如下状态,也没有报错,啥也看不出来.
在默认状态下是无法看出东西,此时我们只需要点击全部中断按钮,则死锁的相关信息都会展示出来,如下图.
可以看到已经提示检测到死锁了,同时在调用堆栈窗口中还可以通过双击切换具体发生死锁的代码.
我们再切换至并行堆栈调试窗口,和调用堆栈相比,并行堆栈窗口更偏向图形化,并且发生死锁的两个线程方法都有体现出来,同样可以通过双击切换到具体代码,如下图:
下面我们再来看看线程调试窗口,如下图,可以发现前面有两个箭头,其中黄色箭头表示当前选中的发生死锁的代码,图中绿色选中代码,灰色箭头表示第一个发生死锁的代码。可以通过双击当前窗口中行进行发生死锁代码的切换,如下图:
当然还可以通过其他方式排查死锁,比如分析dump文件,这里就不深入了,后面有机会再单独讲解.
下面介绍几种避免死锁的指导思想.
顺序加锁就是为了避免产生循环等待,如果大家都是先锁定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");
}
}
}
我们看看代码执行结果.
我们可以使用一些其他锁机制,比如使用Monitor.TryEnter方法尝试获取锁,如果在指定时间内没有获取到锁,则释放当前所拥有的锁,以此来避免死锁.
我们可以通过Thead结合CancellationToken实现超时机制,避免线程无限等待。当然可以直接使用Task,因为Task本身就支持CancellationToken,提供了内置的取消支持使用起来更方便.
一个线程在拥有一个锁的同时尽量避免再去申请另一个锁,这样可以避免循环等待.
上面是使用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 。
最后此篇关于并发编程-死锁的产生、排查与解决方案的文章就讲到这里了,如果你想了解更多关于并发编程-死锁的产生、排查与解决方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我只是想知道要安装哪个版本的 Visual Studio 2010(专业版或高级版)提示升级项目.. 项目包括:asp.net mvc、数据库和silverlight。 最佳答案 通常,由不同版本的相
几种通过 iproute2 来打通不同节点间容器网络的方式 几种通过 iproute2 来打通不同节点间容器网络的方式 host-gw ipip vxlan 背景 之前由于需
目录 前言 1、TypeHandler 简介 1.1转换步骤 1.2转换规则 2、JSON 转换 3、枚举转换 4、文章小结
目录 前言 1、常见 key-value 2、时效性强 3、计数器相关 4、高实时性 5、排行榜系列 6、文章小结 前言 在笔者 3 年的
目录 前言 四、技术选型 五、后端接口设计 5.1业务系统接口 5.2App 端接口 六、关键逻辑实现 6.1Red
目录 前言 一、需求分析 1.1发送通知 1.2撤回通知 1.3通知消息数 1.4通知消息列表 二、数据模型设计
目录 前言 一、多租户的概念 二、隔离模式 2.1独立数据库模式 2.2共享数据库独立数据架构 2.3共享数据库共享数据架构
导读: 虽然锁在一定程度上能够解决并发问题,但稍有不慎,就可能造成死锁。本文介绍死锁的产生及处理。 死锁的产生和预防 发生死锁的必要条件有4个,分别为互斥条件、不可剥夺条件、请求与保持条件和循环等待条
在浏览网页后,我找不到任何功能来执行此操作,我有可行的个人解决方案。也许它对某人有用。 **使用 Moment 插件转换日期。***moment(currentPersianDate).clone()
是否有一种解决方案可以很好地处理数字(1-10)手写?我试过tesseract,但我得到的只是垃圾。 理想情况下是 OSS,但商业也可以。 最佳答案 OpenCV 现在带有手写数字识别 OCR 示例。
在服务器应用程序上,我们有以下内容:一个称为 JobManager 的单例类。另一个类,Scheduler,不断检查是否需要向 JobManager 添加任何类型的作业。 当需要这样做时,调度程序会执
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 5年前关闭。 Improve this qu
当您尝试从 GitHub 存储库安装某些 R 包时 install_github('rWBclimate', 'ropensci') 如果您遇到以下错误: Installing github repo
问题在以下链接中进行了描述和演示: Paul Stovell WPF: Blurry Text Rendering www.gamedev.net forum Microsoft Connect: W
我正在寻找一种解决方案,使用标准格式 a × 10 b 在科学记数法下格式化 R 中的数字。一些同行评审的科学期刊都要求这样做,并且手动修改图表可能会变得乏味。 下面是 R 标准“E 表示法”的示例,
已编辑解决方案(如下...) 我有一个启动画面,它被打包到它自己的 jar 中。它有效。 我可以通过以下方式从另一个 java 应用程序内部调用 Splash.jar: Desktop.getDesk
什么是创建像 PageFlakes 或 iGoogle 这样的门户网站的好框架/包? ?我们希望创建一个为员工提供 HR 服务的员工/HR 门户,但我们也需要一种足够灵活的产品,以便我们可以使用它来为
我正在寻找一种解决方案,使用标准格式 a × 10 b 在科学记数法下格式化 R 中的数字。一些同行评审的科学期刊都要求这样做,并且手动修改图表可能会变得乏味。 下面是 R 标准“E 表示法”的示例,
如何将 solr 与 heritrix 集成? 我想使用 heritrix 归档一个站点,然后使用 solr 在本地索引和搜索该文件。 谢谢 最佳答案 使用 Solr 进行索引的问题在于它是一个纯文本
完整日历不包含工作时间功能选项(在任何一天的议程 View 中选择第一行和最后一行 - 例如公司不工作)。我做到了类似的事情: viewDisplay: function(view){
我是一名优秀的程序员,十分优秀!