首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 避免執行緒死鎖實戰:鎖順序一致性與 lock 範圍控制

     編輯:關於C#

死鎖成因分析

多數死鎖源於多執行緒以不一致順序取得相同資源鎖。經典範例:轉帳操作需同時鎖定「轉出帳戶」與「轉入帳戶」。

錯誤範例:不一致鎖順序

public void Transfer(Account from, Account to, decimal amount)
{
    Monitor.Enter(from);  // 執行緒 A 先鎖 from,執行緒 B 先鎖 to
    Monitor.Enter(to);    // 互相等待對方釋放 → 死鎖
    try { /* 業務邏輯 */ }
    finally
    {
        Monitor.Exit(from);
        Monitor.Exit(to);
    }
}

當執行緒 A 執行 Transfer(A, B, 100)、執行緒 B 執行 Transfer(B, A, 50) 時,雙方各持一鎖、各等一鎖,形成循環等待。

解決方案一:統一全域鎖順序

為所有可被鎖定的物件建立固定排序規則(如依物件 ID、記憶體位址、或固定欄位),所有執行緒嚴格遵循此順序取得鎖。

改良版:依帳戶 ID 排序

public void Transfer(Account from, Account to, decimal amount)
{
    // 依 ID 大小決定鎖順序,確保全域一致
    Account first = from.Id < to.Id ? from : to;
    Account second = from.Id < to.Id ? to : from;

    Monitor.Enter(first);
    try
    {
        Monitor.Enter(second);
        try
        {
            // 業務邏輯:扣款、存入
            from.Balance -= amount;
            to.Balance += amount;
        }
        finally { Monitor.Exit(second); }
    }
    finally { Monitor.Exit(first); }
}

解決方案二:擴大鎖範圍至單一物件(粗粒度鎖)

若業務允許,可用單一靜態物件或專用鎖物件保護整個關鍵區段,犧牲平行度換取簡單與安全。

範例:專用鎖物件

private static readonly object _transferLock = new object();

public void Transfer(Account from, Account to, decimal amount)
{
    lock (_transferLock) // 同一時間僅允許一筆轉帳
    {
        from.Balance -= amount;
        to.Balance += amount;
    }
}
策略優點缺點適用場景
統一鎖順序保留細粒度平行度需嚴格規範、程式碼較繁高併發、資源爭用明確
單一全域鎖程式碼極簡、零死鎖風險嚴重限制平行度低併發、操作極快、或暫時方案

進階技巧:Monitor.TryEnter 避免無限等待

public bool TryTransfer(Account from, Account to, decimal amount, int timeoutMs = 1000)
{
    Account first = from.Id < to.Id ? from : to;
    Account second = from.Id < to.Id ? to : from;

    if (Monitor.TryEnter(first, timeoutMs))
    {
        try
        {
            if (Monitor.TryEnter(second, timeoutMs))
            {
                try
                {
                    from.Balance -= amount;
                    to.Balance += amount;
                    return true;
                }
                finally { Monitor.Exit(second); }
            }
        }
        finally { Monitor.Exit(first); }
    }
    return false; // 取得鎖超時,避免死鎖
}

最佳實務清單

  1. 鎖順序一致化:建立團隊規範,所有多重鎖依固定規則排序
  2. 縮小鎖範圍:lock 區塊內只放必要邏輯,避免 I/O、呼叫外部方法
  3. 避免巢狀鎖:重構程式碼減少同時持有多把鎖的情況
  4. 使用較高層級同步 primitives:SemaphoreSlim、ReaderWriterLockSlim、ConcurrentDictionary 等
  5. 考慮無鎖資料結構:Interlocked、System.Threading.Channels、Immutable Collections
  6. 死鎖偵測:引入 Monitor.TryEnter 超時機制,記錄日誌便事後分析

結語

死鎖本質是「循環等待」,核心對策是打破循環等待條件。最穩健的做法是架構層面規範鎖順序,搭配 TryEnter 超時保護;對於低爭用場景,單一全域鎖亦可接受。切記:lock(this) 或 lock(typeof(Type)) 易被外部干擾,建議改用 private readonly object _lock = new object() 專用鎖物件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved