论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
.Net
›
并发编程 - 死锁的产生、排查与解决方案 ...
并发编程 - 死锁的产生、排查与解决方案
徐锦洪
金牌会员
|
2024-12-26 22:42:41
|
显示全部楼层
|
阅读模式
楼主
主题
831
|
帖子
831
|
积分
2493
在多线程编程中,死锁是一种非经常见的标题,稍不留意大概就会产生死锁,今天就和大家分享死锁产生的原因,如何排查,以及解决办法。
线程死锁通常是因为两个或两个以上线程在资源争取中,形成循环等候,导致它们都无法继承执行各自后续操纵的征象。
我们结合下图简朴举个例子,线程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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
徐锦洪
金牌会员
这个人很懒什么都没写!
楼主热帖
彻底卸载SQL Server
马丽明:选择超融合架构的三个要素 ...
Oracle夺命连环25问,你能坚持第几问? ...
java数据库开发与实战应用,2022最值得 ...
漏洞扫描工具nessus、rapid7 insightvm ...
【计算机网络】TCP为什么需要3次握手 ...
c# 实现定义一套中间SQL可以跨库执行的 ...
学了这么久的高并发编程,连Java中的并 ...
p6 BufferedInputStream 和 BufferedOu ...
WPF工控组态软件之冷却塔和空气压缩机 ...
标签云
挺好的
服务器
快速回复
返回顶部
返回列表