死鎖是多線程編程中的經典噩夢:兩個或多個線程循環等待彼此持有的鎖,導致系統永久阻塞。本文通過經典「哲學家進餐」式代碼演示死鎖形成,系統講解四大避免策略——鎖順序法、鎖超時、非阻塞嘗試、細粒度鎖/無鎖結構,並對比 Lock、RLock、Condition、threading.Semaphore 等同步原語的適用場景。
破壞任一條件即可避免死鎖。
原則:系統中所有鎖編碼(id、內存地址、名稱),線程必須按遞增順序獲取鎖,釋放時按遞減順序。
適用於鎖集合固定、可預知的場景。
超時後主動釋放已持有鎖,打破死鎖條件。需業務邏輯支援「放棄並重試」。
acquire(blocking=False)配合指數退避重試,適合高併發、低衝突場景。
RLock 解決同一線程重入RLock(可重入鎖)允許同一線程多次 acquire,需對應次數 release。適用於遞歸函數或同線程內多層庫調用共享鎖。
注意:RLock 不能解決跨線程死鎖,僅解決重入問題。
| 原語 | 適用場景 | 優勢 |
|---|---|---|
threading.Condition | 生產者-消費者、狀態變更通知 | 內建鎖 + wait/notify,避免忙等待 |
threading.Semaphore | 資源池限流(連接池、線程池) | 計數器語義直觀 |
threading.Event | 一次性信號廣播 | 簡單輕量 |
threading.Barrier | 多線程同步屏障 | 集合點同步 |
queue.Queue | 線程間數據傳遞 | 內建鎖 + 滿/空阻塞,無鎖編程 |
concurrent.futures | 任務提交/結果獲取 | 抽象線程池,Future 模式 |
tuple、frozenset、dataclass(frozen=True),共享無需鎖。queue.Queue、collections.deque(CPython GIL 保證單字節碼原子)、concurrent.futures。asyncio 或 multiprocessing 進程隔離,消息傳遞替代共享內存。faulthandler.dump_traceback() 獲取所有線程堆棧。threading.enumerate() 列舉存活線程。pydeadlock、deadlock_detector 運行時檢測。py-spy、gdb 分析卡死進程。Queue/Condition 等高級原語替代裸 Lock。死鎖本質是「並發控制邏輯缺陷」,而非語言缺陷。通過架構層面統一鎖順序、引入高級同步原語、推行無錁設計,可從根源消除死鎖風險,構建穩健的併發系統。