再说多线程(五)——死锁

news2024/11/16 0:52:40

在前面四节中,我们一直没有讨论多线程程序的一个负面问题——死锁,有了一定的基础,现在是时候研究一下死锁了。死锁一定是出现在多线程程序中,单线程是不可能造成死锁的,因为你不可能同时加两把锁。死锁有个简单的例子,假设你和你的邻居关系很好,你们相互放了一把备用钥匙在对方家里,一般来说,只要有一个人带了钥匙,那两个人一定可以各回各家,但是恰好有一天,两人出门都忘记带钥匙,回家时发现对方在门口等着,两个人只能干瞪眼。这就是死锁。可以用下面这个图解释:

如果满足以下条件,则可能会发生死锁:

  1. 互斥:这意味着在特定时间只有一个线程可以访问资源。

  1. 保持并等待:这是一种情况,其中一个线程至少持有一个资源并等待另一个线程已经获取的至少一个资源。

  1. 无抢占:如果一个线程已经获得了一个资源,在它自愿放弃对该资源的控制之前,不能将其从该线程中夺走。

  1. 循环等待:这是两个或多个线程正在等待链中下一个成员获取的资源的情况。


1.死锁举例

为了更好的解释死锁,以及寻找避免死锁的办法,我们还是用程序说话。这里举一个账户转账的例子

首先有一个账户类:

namespace DeadLockDemo
{
    public class Account
    {
        public int ID { get; }
        private double Balance { get; set;}

        public Account(int id, double balance)
        {
            ID = id;
            Balance = balance;
        }
        
        public void WithdrawMoney(double amount)
        {
            Balance -= amount;
        }

        public void DepositMoney(double amount)
        {
            Balance += amount;
        }
    }
}

然后有一个账户经理类:

using System;
using System.Threading;

namespace DeadLockDemo
{
    public class AccountManager
    {
       private Account FromAccount;
       private Account ToAccount;
       private double TransferAmount;

        public AccountManager(Account AccountFrom, Account AccountTo, double AmountTransfer)
        {
            FromAccount = AccountFrom;
            ToAccount = AccountTo;
            TransferAmount = AmountTransfer;
        }

        public void FundTransfer()
        {
            Console.WriteLine($"{Thread.CurrentThread.Name} trying to acquire lock on {FromAccount.ID}");
            lock (FromAccount)
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} acquired lock on {FromAccount.ID}");
                Console.WriteLine($"{Thread.CurrentThread.Name} Doing Some work");
                Thread.Sleep(1000);
                Console.WriteLine($"{Thread.CurrentThread.Name} trying to acquire lock on {ToAccount.ID}");

                lock (ToAccount)
                {
                    FromAccount.WithdrawMoney(TransferAmount);
                    ToAccount.DepositMoney(TransferAmount);
                }
            }
        }
    }
}

因为在转账的时候,为了避免数据不一致,我们分别要锁定扣款账户,以及首款账户,否则,假设在这个过程刚好家人用这个首款账户支付一笔消费,那就会导致账户首款后数额不对。好了我们已经做好准备工作了,我们在main函数中实现转账逻辑:

using System;
using System.Threading;

namespace DeadLockDemo
{
    class Program
    {
        public static void Main()
        {
            Console.WriteLine("Main Thread Started");
            Account Account1001 = new Account(1001, 5000);
            Account Account1002 = new Account(1002, 3000);

            AccountManager accountManager1 = new AccountManager(Account1001, Account1002, 5000);
            Thread thread1 = new Thread(accountManager1.FundTransfer)
            {
                Name = "Thread1"
            };

            AccountManager accountManager2 = new AccountManager(Account1002, Account1001, 6000);
            Thread thread2 = new Thread(accountManager2.FundTransfer)
            {
                Name = "Thread2"
            };

            thread1.Start();
            thread2.Start();

            thread1.Join();
            thread2.Join();
            Console.WriteLine("Main Thread Completed");
            Console.ReadKey();
        }
    }
}

运行一下:

Main Thread Started
Thread2 trying to acquire lock on 1002
Thread2 acquired lock on 1002
Thread2 Doing Some work
Thread1 trying to acquire lock on 1001
Thread1 acquired lock on 1001
Thread1 Doing Some work
Thread1 trying to acquire lock on 1002
Thread2 trying to acquire lock on 1001

注意:上面显然不是运行结束,而是出现死锁,卡住了。死锁的原因很简单,两个账户同事向对方转账,导致两个账户都被锁住,无法访问对方对象。

那怎么解决这个问题呢?

好问题!

2.解决死锁

我们在介绍前面Monitor类,提到过一个函数TryEnter(object obj,int milisecondsTimeout)。使用这个函数,我们可以指定线程释放锁的超时时间,如果一个线程长时间持有一个资源,而另一个线程正在等待,那么Monitor会提供一个时间限制,强制释放锁。这样其他线程就会进入临界区。 也就是必须让一个线程拖鞋,有点像两辆车向西在独木桥上,如果两个车都不愿意退让,那么就会一直卡在独木桥上,必须有一个车先退出到桥外,方可通行。修改后的代码如下:

 internal class AccountManager
    {
        private readonly Account FromAccount;
        private readonly Account ToAccount;
        private double TransferAmount;
        private readonly int WaittingTime;
        public AccountManager(Account accountFrom,Account accountTo, double accountTransfer, int waittingTime)
        {
            FromAccount = accountFrom;
            ToAccount = accountTo;
            TransferAmount = accountTransfer;
            WaittingTime = waittingTime;
        }
        public void Transfer()
        {
            Console.WriteLine($"{Thread.CurrentThread.Name} trying to acquire lock on {FromAccount.ID}");
            lock (FromAccount)
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} acquired lock on {FromAccount.ID}");
                Console.WriteLine($"{Thread.CurrentThread.Name} Doing Some work");
                Thread.Sleep(1000);
                Console.WriteLine($"{Thread.CurrentThread.Name} trying to acquire lock on {ToAccount.ID}");
                if(Monitor.TryEnter(ToAccount,WaittingTime))
                {
                    Console.WriteLine($"{Thread.CurrentThread.Name} acquired lock on {ToAccount.ID}");
                    try
                    {
                        FromAccount.WithdrawMoney(TransferAmount);
                        ToAccount.DepositMoney(TransferAmount);
                    }
                    finally
                    {
                        Monitor.Exit(ToAccount);
                    }
                }
                else
                {
                    Console.WriteLine($"{Thread.CurrentThread.Name} Unable to acquire lock on {ToAccount.ID}, So 终止交易.");
                }
                //lock (ToAccount)
                //{
                //    FromAccount.WithdrawMoney(TransferAmount);
                //    ToAccount.DepositMoney(TransferAmount);
                //}
            }
        }
    }

然后运行结果如下:

Main Thread Started
Thread1 trying to acquire lock on 1001
Thread1 acquired lock on 1001
Thread1 Doing Some work
Thread2 trying to acquire lock on 1002
Thread2 acquired lock on 1002
Thread2 Doing Some work
Thread1 trying to acquire lock on 1002
Thread2 trying to acquire lock on 1001
Thread1 Unable to acquire lock on 1002, So 终止交易.
Thread2 acquired lock on 1001
Main Thread Completed

这里要说明的是,这个方案并不完美,读者应该可以发现我增加了一个等待时间属性,就是为了让两个经理类等待时间不一样,否则会出现两个转账在等待相同时间后,同时终止交易。

另一种解决方案是,两个线程在获取锁的时候,保持步调一致,即都先获取ID值小的账户,这样没有获得的就只能等获取的运行结束,释放锁后再去执行。经理代码变为:

 public class CleverAccountManager
    {
        private readonly Account FromAccount;
        private readonly Account ToAccount;
        private double TransferAmount;
      //  private static readonly Mutex mutex = new Mutex();
        
        public CleverAccountManager(Account accountFrom, Account accountTo, double accountTransfer)
        {
            FromAccount = accountFrom;
            ToAccount = accountTo;
            TransferAmount = accountTransfer;
        }
        public void Transfer()
        {
            object _lock1, _lock2;
            (_lock1,_lock2)=FromAccount.ID<ToAccount.ID?(FromAccount,ToAccount):(ToAccount,FromAccount);
            Console.WriteLine($"{Thread.CurrentThread.Name} trying to acquire lock on {((Account)_lock1).ID}");
            lock(_lock1)
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} acquired lock on {((Account)_lock1).ID}");
                Console.WriteLine($"{Thread.CurrentThread.Name} Doing Some work");
                Thread.Sleep(3000);
                Console.WriteLine($"{Thread.CurrentThread.Name} trying to acquire lock on {((Account)_lock2).ID}");
                lock (_lock2)
                {
                    Console.WriteLine($"{Thread.CurrentThread.Name} acquired lock on {((Account)_lock2).ID}");
                    FromAccount.WithdrawMoney(TransferAmount);
                    ToAccount.DepositMoney(TransferAmount);
                }
            }
        }

    }

运行结果如下:

Main Thread Started
Thread3 trying to acquire lock on 1001
Thread3 acquired lock on 1001
Thread3 Doing Some work
Thread4 trying to acquire lock on 1001
Thread3 trying to acquire lock on 1002
Thread3 acquired lock on 1002
Thread4 acquired lock on 1001
Thread4 Doing Some work
Thread4 trying to acquire lock on 1002
Thread4 acquired lock on 1002
Main Thread Completed

在实际项目中,死锁的问题不仅仅是一个技术问题,也是一个业务问题,因为从技术上来说,就是最开始那张图,说出花来,也就是怎样加锁解锁,当然你可以说不加锁不行吗?这也是可以的,现在不是有无锁数据结构吗?但是无论技术怎么变,总归是要为实际项目服务的。我们需要在学习和实践中不断总结!

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

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

相关文章

《软件工程》课程四个实验的实验报告(《可行性研究与项目计划》《需求分析》《系统设计》《系统实现》)

实验1《可行性研究与项目计划》 实验学时&#xff1a; 2 实验地点&#xff1a; 任意 实验日期&#xff1a; 12月15日 一、实验目的 了解&#xff1a;软件项目可行性研究及项目计划的基本原理与方法&#xff1b;掌握&#xff1a;Visio等工具进行可…

【尚硅谷】Java数据结构与算法笔记06 - 算法复杂度详解

文章目录一、算法的时间复杂度1.1 度量算法执行时间的两种方法1.1.1 事后统计1.1.2 事前估算1.2 时间频度1.2.1 基本介绍1.2.2 举例说明&#xff1a;基本案例1.2.3 举例说明&#xff1a;忽略常数项1.2.4 举例说明&#xff1a;忽略低次项1.2.5 举例说明&#xff1a;忽略系数1.3 …

WebServer传输大文件致客户端自动关闭

程序运行在云服务器上, Ubuntu 20.04LTS系统&#xff0c;用浏览器测试能正常打开页面&#xff0c;请求一般的html文本和几十kb的小图片无问题&#xff0c;接着放了一个1.63MB&#xff08; 1714387字节&#xff09;的网上找的图过去&#xff0c;客户端图没加载完就自动断连了&am…

如何搭建一个专业的企业知识库

当客户跟你达成合作关系后&#xff0c;需要持续的关系维护&#xff0c;在一定的销售点&#xff0c;定期和客户沟通&#xff0c;据调查&#xff0c;赢得一个新客户的成本可能是保留一个现有客户的5到25倍&#xff0c;作为营销策略&#xff0c;客户服务支持必须满足他们的期望。建…

Linux小黑板(7):再谈动静态

"我看到&#xff0c;久违的晴朗啊"一、什么是动静态库在本栏目前面的篇幅也提到过这个概念&#xff0c;因此本小节就小小地回顾一番。在linux下:静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。动态库(.so):程序在运行的时候才去链接动态库的代码&am…

【npm报错】解决invalid json response body at https://registry.npmjs.org

报错信息&#xff1a; npm ERR! code FETCH_ERROR npm ERR! errno FETCH_ERROR npm ERR! invalid json response body at https://registry.npmjs.org/riophae%2fvue-treeselect reason: Invalid response body while trying to fetch https://registry.npmjs.org/riophae%2f…

从粪便菌群移植到下一代有益菌:Anaerobutyricum soehngenii为例

谷禾健康 我们知道&#xff0c;肠道微生物群对人类健康和福祉很重要&#xff0c;调节宿主代谢&#xff0c;塑造免疫系统并防止病原体定植。 通过粪便微生物群移植&#xff08;FMT&#xff09;恢复平衡多样的微生物群&#xff0c;已成为研究疾病发病机制中微生物群因果关系的潜在…

Spring Cloud Gateway 之限流

文章目录一、常见的限流场景1.1 限流的对象1.2 限流的处理方式1.3 限流的架构二、常见的限流算法2.1 固定窗口算法&#xff08;Fixed Window&#xff09;2.2 滑动窗口算法&#xff08;Rolling Window 或 Sliding Window&#xff09;2.3 漏桶算法&#xff08;Leaky Bucket&#…

CSS3属性之text-overflow:ellipsis详解

1.text-overflow: 默认值:clip; 适用于:所有元素 clip:当前对象内文本溢出时不显示省略标记(…),而是将溢出部分裁剪。 ellipsis:当对象内文本一处时显示省略标记(…)。 当然这还是不够的&#xff0c;需要加点调料才能出现效果: 那就是配合 overflow:hidden white-space:…

高级树结构之红黑树初识

文章目录一 红黑树简介二 探究变色、旋转操作的时机三 总结一 红黑树简介 通过在插入几点时维护数的平衡&#xff0c;这样就不会出现极端情况&#xff0c;使得整棵树的查找效率急剧降低。但是这样造成系统开销过大&#xff0c;因为一旦平衡因子的绝对值超过一就失衡&#xff0…

iOS 界面尺寸居然跟实际机型不符!

0x00 前言 日常搬砖过程中&#xff0c;一条日志&#xff0c;让我对手里的 iPhone 6 Plus 产生了怀疑&#xff1f; 这是 6P&#xff0c;怎么尺寸变成 6 了呢&#xff1f; 0x01 对比 手机连上电脑&#xff0c;通过 Xcode 查看 Window 菜单&#xff1a;Devices and Simulators …

Insight Enterprises EDI 855 采购订单确认报文详解

本文着重讲述Insight EDI项目中Insight回复给采购商的X12 855报文&#xff08;采购订单确认&#xff09;。 在此前的文章如何读懂X12报文中&#xff0c;我们对X12报文的结构已经做了详细的介绍&#xff0c;本文将带大家深入了解X12 855采购订单确认报文。 下图为Insight X12 …

单绞机张力开环控制(绞臂行星差速机构)

PLC的开环和闭环张力控制算法,可以参看下面的文章链接: PLC张力控制(开环闭环算法分析)_plc张力控制程序_RXXW_Dor的博客-CSDN博客里工业控制张力控制无处不在,也衍生出很多张力控制专用控制器,磁粉制动器等,本篇博客主要讨论PLC的张力控制相关应用和算法,关于绕线机的…

Java学习之final关键字

目录 一、基本介绍 二、使用final的四种情况 第一种 第二种 第三种 第四种 三、细节 第一点 第二点 第三点 第四点 第五点 第六点 第七点 第八点 第九点 四、练习 第一题 第二题 分析 一、基本介绍 final:最终的&#xff0c;最后的 final 可以修饰类…

图形编辑器:旋转选中的元素

大家好&#xff0c;我是前端西瓜哥。 最近更文比较少&#xff0c;是因为本人在做个人开源项目&#xff0c;用 Canvas 做一个设计工具&#xff0c;做个乞丐版 figma。期间遇到了不少问题&#xff0c;在这里记录一下。 今天开始会恢复高频更新的&#xff0c;一两天一更。内容主…

Linux网络服务管理防火墙详解端口问题

每次配置访问服务器都会一团迷雾&#xff0c;今天来尝试弄清楚同时借鉴一下大佬的博文当做笔记 文章目录防火墙简介一、防火墙基础二、iptables防火墙策略iptables 命令格式&#xff1a;iptables案例1.查看开放的端口2.开放端口&#xff08;此处以80端口为例&#xff09;3.关闭…

超级详细的PMP复习方法,3A拿下考试不发愁!

如果问我是怎么一次性通过考试的&#xff0c;那绝对不只是运气&#xff0c;没有一点基本的实力怎么有底气通过考试呢&#xff0c;所以今天我们不讲什么刷题技巧&#xff0c;基础不牢靠将技巧都是没用的&#xff0c;今天我们先讲讲要怎么巩固基础&#xff0c;给到朋友们分享一些…

kaggle平台学习复习笔记 | 特征工程

目录数值字段roundBox/Bins类别字段onehotLabelEncoderOrdinal EncodingBinaryEncoderFrequency/Count EncodingMean/Target Encoding日期字段特征筛选feature_importances_利用方差利用相关性利用线性模型迭代消除排列重要性(Permutation Importance)特征工程决定了模型精度的…

MATLAB-RBF神经网络例1

采用所描述的系统中﹐假设真实质量为m2,在仿真中,初始值为,采用的自适应律为&#xff1a;设定参数为y0.5,10,25,6&#xff0c;分别设定参考位置为r(t)0,r(t )sin(4t) ,初始条件为&#xff0c;。图1.1和图1.2为指令r(t)0时控制效果,图1.3和图1.4为指令r(t)sin(4t )时的控制效果。…

verilog学习笔记- 12)触摸按键控制LED灯实验

目录 简介&#xff1a; 实验任务: 硬件设计: 程序设计: 下载验证&#xff1a; 简介&#xff1a; 触摸按键主要可分为四大类&#xff1a;电阻式、电容式、红外感应式以及表面声波式。根据其属性的不同&#xff0c;每种触摸按键都有其合适的使用领域。 电阻式触摸按键&#…