死鎖 是多線程/多進程編程中的經典災難:多個執行單元因爭奪資源而相互無限等待,導致系統整體停滯。本文通過生活類比、兩大核心成因分析、偽代碼演示,建立對死鎖的直觀認知,為後續實戰代碼排查打基礎。
將運行的程序比作行駛的汽車,共享資源比作馬路。兩輛車相撞停在路中,佔用馬路不放,後車無法通行,最終整條路癱瘓。這就是死鎖:佔有資源並等待其他資源,形成循環等待。
線程對已加鎖的互斥量再次加鎖。若互斥量非遞歸型,線程將阻塞在自己手中,永無解脫之日。
thread_func() {
lock(mutex); // 第一次加鎖
// do something
lock(mutex); // 第二次加鎖 → 死鎖!自己等自己
// do another
unlock(mutex);
unlock(mutex);
}
避免簡單:代碼審查易發現;或使用 pthread_mutex_trylock 非阻塞嘗試。
線程 A 持有鎖 1 等鎖 2;線程 B 持有鎖 2 等鎖 1。雙方僵持,系統癱瘓。這是最隱蔽、最常見的死鎖,因分佈在不同線程、不同代碼文件中,極難靜態發現。
thread_funcA() {
lock(mutex1); // A 持有 1
// do something
lock(mutex2); // A 等 2
// do another
unlock(mutex2);
unlock(mutex1);
}
thread_funcB() {
lock(mutex2); // B 持有 2
// do something
lock(mutex1); // B 等 1
// do another
unlock(mutex1);
unlock(mutex2);
}
| 條件 | 說明 | 破壞策略 |
|---|---|---|
| 互斥 | 資源一次只能被一個線程佔有 | 難以破壞(硬件/內核特性) |
| 佔有並等待 | 線程持有資源同時申請新資源 | 一次性申請所有資源 |
| 不可搶佔 | 資源只能由持有者主動釋放 | 允許搶佔/超時釋放 |
| 循環等待 | 存在線程環,每個等待下一個持有的資源 | 統一資源編號,按序申請 |
破壞任一條件即可避免死鎖,工程上最常用的是破壞「循環等待」(統一加鎖順序)。
死鎖雖可怕,但成因清晰、規律可循:
下一回將展示如何將偽代碼轉為真實 C 代碼演示死鎖,並介紹 pthread_mutex_trylock 避免自我死鎖的實戰技巧。