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

並發編程 11 大隱患深度解析:從資料競爭到優先級反轉的完整防禦指南

     編輯:關於.NET

前言:多核時代的並發挑戰

隨著多核普及,客戶端也必須面對並發。正確的並發代碼需遵循額外規則:同步存取共享資料、協調執行緒推進。同步引入非確定性,難測試、難預測。本文梳理 11 大常見隱患與應對模式。

一、安全隱患:導致崩潰與資料損壞

1. 資料競爭 / 競爭條件

多執行緒同時讀寫共享記憶體,且至少一個寫入。經典計數器失效:

static int s_curr = 0;
int GetNext() { return s_curr++; } // 非原子:讀-增-寫三步驟

解法:Interlocked.Increment 或 lock。

2. 遺忘同步

最簡單的競爭:完全沒加鎖。常見於物件從私有發布為共享時 (發布/釋出)。解法:識別共享點,加入正確同步。

3. 粒度錯誤

鎖範圍太小,無法保護不變量。轉帳範例:

// 錯誤:資金中間狀態丟失
Withdraw(a, delta); Deposit(b, delta);

// 正確:預先鎖定雙帳戶
lock(a.Lock) lock(b.Lock) { Withdraw(a, delta); Deposit(b, delta); }

但引發死鎖風險 (見第 6 項)。

4. 讀寫撕裂

非對齊或超過原生字長 (32/64 位) 讀寫非原子。64 位變數在 32 位系統可能讀到高低半字混合值。解法:確保對齊、用 Interlocked.Read 或鎖保護。

5. 無鎖重排序

編譯器/CPU 重排序記憶體操作導致邏輯錯誤。雙重檢查鎖定失效經典案例。解法:Thread.MemoryBarrier、volatile、或干脆用鎖。未徹底理解記憶體模型前,避免無鎖編程。

6. 遞歸鎖與重入

CLR Monitor 支援遞歸。若鎖區域呼叫虛擬方法/委派,可能觸發重入破壞不變量。最佳實踐:鎖內不呼叫動態代碼。

二、活性隱患:系統停止推進

7. 死鎖

循環等待鏈。解法:一致鎖獲取順序 (如按帳戶 ID 排序)。

if (a.Id < b.Id) { lock(a) lock(b) ... } else { lock(b) lock(a) ... }

8. 喚醒遺失

事件信號被忽略 (自動重置事件重複 Set)、或條件變數用 Pulse 應用 PulseAll 場景。檢查等待條件重置邏輯。

9. 鎖爭用

熱鎖導致排隊、線程飢餓。ASP.NET 常見於短時工作項大量排隊。緩解:減少鎖持有時間、分解熱鎖、無鎖資料結構。

10. 蜂擁效應

大量線程同時被喚醒搶佔 CPU,多數發現條件不滿足再睡。手動重置事件觸發單項工作時易發生。解法:信號量、工作佇列模式。

11. 兩步舞曲 / 優先級反轉

  • 兩步舞曲:持有鎖時喚醒事件,被喚醒線程發現仍需等鎖,雙重上下文切換。
  • 優先級反轉:低優先級持有鎖,高優先級等待,中優先級搶佔導致高優先級間接等待。Windows Balance Set Manager 4 秒後強制提升,但延遲不可接受。盡量不調整執行緒優先級。

三、實現安全性的設計模式

不可變性

  • 構建後不變 → 無衝突 → 無需同步
  • C# readonly、F# 預設不可變
  • 淺層 vs 深層不可變
  • WPF Freezable 實現動態不可變
public class ImmutableStack {
    readonly T value; readonly ImmutableStack next; readonly bool empty;
    public ImmutableStack Push(T v) => new ImmutableStack(v, this);
    public ImmutableStack Pop(out T v) { v = value; return next; }
}

純度

  • 純函數:唯讀共享不可變狀態、唯寫區域變數、只呼叫純函數
  • 天生執行緒安全,無需同步
  • Haskell 強制純度 (Monad 隔離副作用)

隔離性

  • 狀態限制在單一執行緒 → 無需同步 → 本質可擴展
  • 關鍵:追蹤物件何時發布為共享 (靜態欄位、啟動執行緒傳遞、閉包捕獲)
  • 私有化:共享→私有,需謹慎同步

總結

並發核心難點在於**共享可變狀態**。採用不可變資料、純函數、執行緒隔離三大模式,從根源消除同步需求。現代語言 (F#、Rust、Go) 皆以此為核心設計哲學。

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