c#多线程死锁示例

电脑杂谈  发布时间:2020-06-07 14:17:13  来源:网络整理

避免死锁_死锁举例_银行家算法避免死锁

在某些情况下,可以避免死锁. 避免死锁的三种技术:

锁定顺序(线程按特定顺序锁定)锁定时间限制(线程尝试获取锁定时添加了特定时间限制. 如果超过该时间限制,则放弃对锁定的请求并将其锁定释放)死锁检测

当多个线程需要相同的锁,但以不同的顺序锁定它们时,很容易发生死锁.

如果可以确保所有线程以相同顺序获取锁,则不会发生死锁. 看这个例子:

Thread 1:
  lock A 
  lock B
Thread 2:
   wait for A
   lock C (when A locked)
Thread 3:
   wait for A
   wait for B
   wait for C

避免死锁_银行家算法避免死锁_死锁举例

如果线程(例如线程3)需要一些锁,则它必须以一定顺序获取锁. 只有获得序列前面的锁之后,才能获得序列后面的锁.

例如,线程2和线程3只能在获取锁A之后尝试获取锁C(译者注: 获取锁A是获取锁C的必要条件). 由于线程1已经拥有锁A,因此线程2和3需要等待直到锁A被释放. 然后他们必须成功锁定A才能尝试锁定B或C.

顺序锁定是一种有效的防止死锁的机制. 但是,此方法要求您事先了解所有可能使用的锁(译者注: 这些锁的正确顺序),但总有一些情况是不可预测的.

另一种避免死锁的方法是在尝试获取锁时添加超时时间,这意味着,如果在尝试获取锁的过程中超过了时间限制,则线程将放弃锁请求. 如果线程在给定的时间限制内未成功获取所有必需的锁,它将回滚并释放所有获取的锁,然后等待随机的时间段再尝试. 此随机等待时间为其他线程提供了尝试获取相同锁的机会,并允许应用程序在未获取锁时继续运行. (译者注: 锁定超时后,您可以继续运行以执行其他操作,然后返回并重复上一个锁定的逻辑. )

下面是一个示例,该示例显示了一个场景,其中两个线程尝试以不同的顺序获取相同的两个锁,先退后再在发生超时后重试:

银行家算法避免死锁_避免死锁_死锁举例

Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.
Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.

在上面的示例中,线程2重试以比线程1早200毫秒锁定,因此它可以首先成功地获得两个锁定. 此时,线程1尝试获取锁A并处于等待状态. 当线程2结束时,线程1也可以成功获得这两个锁(除非线程2或其他线程程1成功获得这两个锁之前获得了其中一些锁).

应该注意,由于锁定超时,我们不能假定在这种情况下发生了死锁. 这也可能是因为获得锁的线程(导致其他线程超时)需要很长时间才能完成其任务.

此外,如果有许多线程同时争用同一批资源,即使存在超时和回滚机制,也可能仍然导致这些线程反复尝试,但从未获得锁. 如果只有两个线程,并且重试超时设置在0到500毫秒之间,则可能不会出现这种现象,但是如果是10或20个线程,情况就不同了. 因为这些线程等待相等的重试时间的可能性要高得多(或者接近,以至于它们会引起问题).

(译者注: 超时和重试机制是为了避免同时竞争,但是当线程很多时,两个或更多线程的超时时间将相同或接近的可能性很高. 因此,即使由于竞争而导致超时,但由于超时时间相同,它们将同时开始重试,从而导致新一轮的竞争,从而带来新的问题. )

避免死锁_死锁举例_银行家算法避免死锁

此机制存在问题. 无法在Java中为同步块设置超时. 您需要创建一个自定义锁,或者使用Java 5中java.util.concurrent包下的工具. 编写自定义锁类并不复杂,但这超出了本文的范围. 随后的Java并发系列将介绍自定义锁的内容.

死锁检测是一种更好的防止死锁的机制,主要用于无法实现顺序锁并且锁超时不可行的情况.

每当线程获得锁时,它将被记录程和与锁相关的数据结构(映射,图形等)中. 此外,每当线程请求锁定时,也需要将其记录在此数据结构中.

当线程未能请求锁时,线程可以遍历锁关系图以查看是否发生死锁. 例如,线程A请求锁7,但此时锁7由线程B持有. 这时,线程A可以检查线程B是否请求了线程A当前持有的锁定. 如果线程B确实有这样的请求,则发生了死锁(线程A拥有锁1并请求锁7;线程B拥有锁. 7,并请求锁定1).

当然,死锁通常比两个线程持有彼此的锁要复杂得多. 线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D再等待线程A. 为了使线程A检测死锁,它需要逐步检测B所请求的所有锁. 从线程B所请求的锁开始,线程A找到线程C,然后找到线程D,并找到线程D所请求的锁. 由线程A本身持有. 这是当它知道发生死锁的时候.

避免死锁_银行家算法避免死锁_死锁举例

以下是四个线程(A死锁举例,B,C和D)之间的锁拥有和请求之间的关系图. 这样的数据结构可用于检测死锁.

那么当检测到死锁时这些线程会做什么?

一种可行的方法是释放所有锁,回滚并等待随机的一段时间,然后重试. 这类似于简单的锁定超时. 区别在于它仅在发生死锁时回滚,而不是因为锁定请求超时. 尽管存在回滚和等待,但是如果有大量线程在争用同一批锁,它们仍将反复死锁(编者注: 原因类似于超时,并且从根本上不能减少竞争).

一个更好的解决方案是设置这些线程的优先级,并让一个(或几个)线程回退. 其余线程继续保持所需的锁死锁举例,就好像没有死锁发生一样. 如果赋予这些线程的优先级是固定的,则同一批线程将始终具有更高的优先级. 为避免此问题,可以在发生死锁时设置随机优先级.


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-237258-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...