1. 首页
  2. >
  3. 编程技术
  4. >
  5. C#

C#中多线程的那点事-死锁

我们讲述了多线程编程过程中,一种限制多个线程对资源的同时访问的技术——锁。

小明同学,上周未和家人出去游玩去了。刚学了锁的用法,小明终于完善的模拟出了早餐店的流水线,所以他游玩很开心。但是回家的路上,却遇到了烦心事!

由于天气很好,小明一家人游玩到了天黑才驱车回家。正值交通拥堵的时候,在他们即将行进到一个环岛的时候,交通完全堵死了。

C#中多线程的那点事-死锁

传说中的堵死

小明在车上看着道路资源被无限的占用着,联想到多线程编程中的锁:要是限制一下进入环岛的车辆的数量,是不是就不会出现这种无限的堵死在状态呢!

由于车辆太多,已经进入环岛的车辆,出环岛的路被堵死,无法出去,无法释放占用的道路资源。想要进入环岛的车辆,却又因为无法进入环岛而又一直占用着环岛的出口。这不就是个死循环嘛!

在堵了一个小时之后,小明一家人终于走过了这个环岛,不一会就到家了。

死锁演示

聪明的小明,直觉告诉他,在多线程中,也可能会出现这种现象。于是他用两个锁对象,模拟了环岛堵死的场景:

class Program {      static long uniqueRes = 0; // 唯一资源     // 锁对象     static object roadOut = new object();     static object roadIn = new object();       static void EnterCircle()     {         lock (roadIn)         {             // 模拟堵在了入口             for (int i = 0; i < 10000; i++)             {                 uniqueRes += 1;             }              lock (roadOut)             {                 // 模拟堵在了出口                 for (int i = 0; i < 10000; i++)                 {                     uniqueRes -= 1;                 }             }         }     }     static void ExitCircle()     {         // 将资源锁上         lock (roadOut)         {             // 模拟堵在了出口处             while (true)             {                 uniqueRes -= 1;             }         }     }      static void Main(string[] args)     {         Console.WriteLine("Hello Thread Dead Locker!");          var t1 = new Thread(EnterCircle);         var t2 = new Thread(ExitCircle);          t1.Start();         t2.Start();          t1.Join();         t2.Join();          Console.WriteLine($"sum: {uniqueRes}");         Console.ReadKey();     } }

运行结果如下:


C#中多线程的那点事-死锁

程序无法结束

果然程序也堵住了。

资源交叉占用导致死锁

今天上课之前,小明向外老师讲述了他们家周末堵车的遭遇,还有他写的模拟程序无法结束的问题。

其实小明模拟的情形,是一种资源的恶意占用。一直占用资源而不归还,导致其他线程无法访问资源。这种情况,在实际编程中,不多见,除非是恶意代码。一般正常的代码,就算长时间占用资源,最终都会归还资源的。

但是有另外一种交叉占用资源导致的死锁问题,要特别小心。我把小明的代码做了一点修改:

class Program {      static long uniqueResA = 0; // 唯一资源       static long uniqueResB = 0; // 唯一资源     // 锁对象     static object A = new object();     static object B = new object();     static void Fun1()     {         lock (A)         {             for (int i = 0; i < 10000; i++)             {                 uniqueResA += 1;             }             lock (B)             {                 for (int i = 0; i < 10000; i++)                 {                     uniqueResB -= 1;                 }             }         }     }     static void Fun2()     {         lock (B)         {             for (int i = 0; i < 10000; i++)             {                 uniqueResB -= 1;             }             lock (A)             {                 for (int i = 0; i < 10000; i++)                 {                     uniqueResA += 1;                 }             }         }     }      static void Main(string[] args)     {         Console.WriteLine("Hello Thread Dead Locker!");          var t1 = new Thread(Fun1);         var t2 = new Thread(Fun2);          t1.Start();         t2.Start();          t1.Join();         t2.Join();          Console.WriteLine($"A: {uniqueResA}  B: {uniqueResB}");         Console.ReadKey();     } } 


运行3次的结果如下:

C#中多线程的那点事-死锁

资源交叉占用可能导致死锁

由于Fun1和Fun2几乎同时执行,刚开始Fun1请求资源A成功,Fun2请求资源B也成功。但是当Fun1开始请求资源B的时候,如果Fun2还没有释放B,就会导致Fun1阻塞。Fun1阻塞又导致其无法归还资源A,进而导致Fun2又无法请求到资源A,进而又导致Fun2阻塞。。。


C#中多线程的那点事-死锁

你不让我也没法让


小明看到这个结果,就举手表示有疑问:为什么第三次执行成功了呢?

我并没有回答小明的问题,而是布置了作业:

1.同学们回家之后,将Fun1和Fun2中的循环中的计算的次数调高和调低,然后观察程序多次执行出现死锁的概率。

2.如何避免出现死锁现象?

3.如果出现死锁,如何解除死锁?

小明似乎明白了什么,但是我立即示意他不要说出来!先验证再说结论!

C#中多线程的那点事-死锁

聪明的小明

附:参考答案

1.循环中,计算次数越多,出现死锁的概率越高

2.a 尽量避免长时间占用资源

2.b 避免资源交叉请求

3.a 有序资源分配法

3.b 银行家算法


踩坑记录

这次发现一个和此前我的理解不同的地方,分享给大家:

请问开启4个线程,同时执行如下的DealRes函数,会导致死锁吗?

static long uniqueRes = 0; // 唯一资源 // 锁对象 static object locker = new object();  static void DealRes() {     // 将资源锁上     lock (locker)     {         // 再来一次lock,会死锁吗?!         lock (locker)         {             for (int i = 0; i < 10000; i++)             {                 uniqueRes += 1;             }         }     } }
SSO的通用标准OpenID Connect
« 上一篇 2020年12月16日 am07:32
5 分钟入门 spring cloud 实战笔记
下一篇 » 2020年12月16日 am07:37

相关推荐