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