如何寫一段死鎖代碼

如何寫一段死鎖代碼

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

Tags: