幾年前,Eric Lippert注意到根據(jù)同樣源代碼進行優(yōu)化構建和非優(yōu)化構建會導致不同的潛在死鎖。這個問題會在C# 4.0中被“修復”。“修復”放在引號當中,是因為解決方式也有它自己的問題。
最初的問題可能來自于編譯器在把IL轉化為機器代碼的時候,根據(jù)你是否打開或關閉優(yōu)化器和調試器,以非一致的行為插入了no-op指令。Lippert提道:
回想一下,lock(obj){body}實際上就是下面代碼的語法:var temp = obj;
Monitor.Enter(temp);
try { body }
finally { Monitor.Exit(temp); }這里的問題是,如果編譯器在Monitor.Enter和受try保護的區(qū)域之間生成了no-op指令,那么運行時就有可能 在Monitor.Enter之后和try之前拋出線程終止異常。在這樣的情形下,finally不會執(zhí)行,那么也就產生了程序鎖泄漏,程序有可能出現(xiàn)死 鎖。如果在非優(yōu)化和優(yōu)化構建中不存在差異,就不存在這個問題。
不過。這個解決方案[譯注:C#
4.0是將Monitor.Enter()移入到try子句中,并在Enter的時候會傳遞一個引用值,標識鎖是否被占用。在finnally子句中,會
首先判斷鎖是否被占用,如果被占用,則釋放鎖。]也有它自己的問題。據(jù)Eric說,“保持一致與不一致相比,完全就是五十步笑百步。它仍然存在很大的問
題...這樣生成的代碼所[譯注:生成的代碼是指編譯器將lock轉換為IL,實際上就相當于使用Monitor的語法]隱含的意義就是認為死鎖程序是可
能 發(fā)生的最糟糕的事情。這種說法未必準確。”
鎖的目的是為了保護可變資源,或者換句話說,是為了避免可變資源的多個潛在用戶訪問資源已被破壞的版本。4.0版本的現(xiàn)有解決方案并沒有包含回滾到 原始狀態(tài)的功能,也沒有保證可變資源的完整性。強行進入lock語句的finally子句、釋放鎖以及允許訪問任意等待 線程(該線程占用了已被破壞的資源),都有可能引發(fā)異常。這一解決方案在結果的一致性、降低死鎖的可能性和對訪問被破壞狀態(tài)可能付出的代價方面,做出了折 衷。該問題尤其在多線程編程中會存在風險。
這個特定的折衷是對兩種糟糕結果的選擇:程序死鎖,還是不再保護重要資源的狀態(tài)。所謂“兩害相權取其輕”,當我們進行多線程編程時,就必須在多個設計決策與權衡中做出一個選擇。
這篇文章反響熱烈, 一些開發(fā)人員認為這類設計問題不只限于多線程,在“安全鎖”和“安全異常”之間也存在不同之處。Lippert也同意多線程只會讓難處理的問題更難,“正 確獲得鎖僅僅是萬里長征的第一步”,你的設計還需要考慮其他各種異常,以及在異常發(fā)生后如何處理它們。大量的回復者指出終止線程的危險性,并部分同意 Lippert所說的“終止異常純粹就是找死”。