如何写一段死锁代码

604次阅读  |  发布于4年以前

如何写一段死锁代码

Intro

上次介绍了如何写一段代码造成 StackOverflow ,今天来玩一下,看如何写一段代码造成死锁

什么是死锁

首先我们需要明确一下什么是死锁,造成死锁需要满足哪些条件,知道这些就可以轻松写出一段死锁代码了

死锁 是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁 状态或系统产生了死锁,这些永远在互相等待的进程称为死锁 进程(线程)。 ---- 百度百科

产生死锁的必要条件:

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

预防死锁方法:

.NET 中的死锁

通常的死锁的示例都是两个锁,多个资源导致的死锁,你有没有想过一个资源也会导致死锁,如何使用一个锁造成死锁呢?思考一下再看下面的代码

private static readonly object Lock = new object();

public static void Test()
{
    lock (Lock)
    {
        Task.Run(TestMethod1).Wait();
    }
}

private static void TestMethod1()
{
    lock (Lock)
    {
        Console.WriteLine("xxx");
    }
}

Test 这个方法中首先获取锁,获取锁成功之后调用另外一个线程去调用 TestMethod1 方法,而 TestMethod1 方法中会再次尝试获取锁,此时因为锁已经被 Test 方法获取而且并没有释放,所以会一直获取不到锁从而造成死锁

其实这种情况还有很多变形,比如说 lock(this)/lock("lockedString") 这种都是比较危险的,所以不推荐使用,我们使用上面的示例做一个变形,使用 lock("lockedString") 来测试一下

public static void Test()
{
    lock ("Lock")
    {
        Task.Run(TestMethod1).Wait();
    }
}

private static void TestMethod1()
{
    lock ("Lock")
    {
        Console.WriteLine("xxx");
    }
}

这样也会造成死锁,因为 lock 的 string 实际上是同一个引用,字符串池(string intern),所以类似于上面的示例,相当于是一个锁,对于 lock(this) 也是类似的,所以通常 lock 是不推荐 lock(this)/lock("string") 这些写法的,对于不同的资源要使用不同的 lock,这样就可以避免上面这个示例的这种情况

More

使用锁的一些注意事项:

  1. 锁要用来锁定对应的访问资源,不同的资源使用不同的锁来访问限制
  2. 锁尽可能使用这样的格式 private readonly object _locker = new object();,是否使用 static 根据需要添加,多个资源有关联时,小心死锁的情况,一次全部分配,任意一个资源分配失败释放另外一个锁以避免死锁
  3. 实现分布式锁的时候指定最大尝试时间,避免死锁避免长时间获取不到锁影响系统性能

在 SQL Server 中会有一个独立的死锁检测的进程,如果发生死锁的情况,会有一个事务会被选择为牺牲品来解决死锁的问题

在通过 Redis 实现分布式锁的时候,通常会指定一个锁的过期时间,过期时间通常是为了避免获取锁成功的系统突然宕机导致锁一直在锁定状态,从而导致其他服务获取锁的时候一直获取失败,除此之外,通常还会指定一个最大等待时间,如果别的服务获取到锁了,正在操作,那么会等待锁释放,但是为了避免死锁,如果长时间获取不到锁的话就会放弃获取锁,直接返回获取锁失败。

除此之外你还了解哪些使用锁的注意事项和避免死锁的常用方法呢,欢迎补充,如果文中有误,欢迎指出,万分感谢。

Reference

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8