切勿鎖定類型對象(轉msdn)

GUI 博士的忠告:切勿鎖定類型對象!

發佈日期: 6/8/2004 | 更新日期: 6/8/2004

2003 年 6 月 5 日

在進行 Internet 或基於 Windows 的開發方面,您遇到過問題或難題嗎?這時,您可以求助於 GUI 博士 ([email protected]);GUI 博士每個月會兩次做客 MSDN,在線回答您的問題。雖然博士忙碌的工作安排使他無法回覆所有的問題,但他會盡可能地在這裏回答更多的問題。如果恰好選中了您的問題,那麼博士會送您一件 GUI 博士 T 恤衫!

摘要:GUI 博士解釋瞭如何避免多線程程序發生死鎖的方法,那就是不要使用鎖定類型對象這種雖然常見但卻是錯誤的方法。(5 頁打印頁)

Dr. GUI's Bits and Bytes...

...是 GUI 博士網絡日記 的標題。但遺憾的是,博士還沒有爲該專欄添加更多的內容,因爲他到加利福尼亞和黃石公園休假去了。但您可以在這裏瞭解他的旅程……,並瞭解博士最近找到的一些新聞鏈接。或者,您可能會喜歡閱讀 Alice in Blibbetland 的故事結局。

總之,如果您有話要說,請在當天的意見欄內發表您的評論。或者給 GUI 博士寫封電子郵件……

希望計算機能具有常識?

GUI 博士最近看到了幾篇新文章,其中一篇是關於名爲 Open Mind Commonsense 的 MIT 項目,這個項目爲數據庫收集常識語句,通過這些語句,計算機的人工智能應用程序可以使用常識。您甚至可以通過單擊上面的鏈接,進行註冊,然後添加簡單句子的常識說明,從而將自己的常識添加到數據庫中。但 GUI 博士認爲,如果能添加自己的常識而無需提示句子,會更加有趣……

現在,步入我們的正題!

爲什麼使用 Lock(typeof(ClassName)) 或 SyncLock GetType(ClassName) 是錯誤的

最近,Microsoft .NET 運行庫的性能設計師及資深 Microsoft 開發人員 Rico Mariani 在一封電子郵件中與 GUI 博士進行了交流,其中提到的一種相當普遍的做法(遺憾的是,這種做法在我們的一些文檔中也曾提到過,雖然我們將進行修改)實際上卻存在着很大的問題。他詢問 GUI 博士能否幫忙發佈消息,告訴程序員不應該採用這種做法。博士當然很樂意幫忙。

這種非常普遍的做法是什麼呢?其實就是對類型對象加鎖。在 C# 中,加鎖的做法是 lock(typeof(ClassName)),其中,ClassName 是某個類的名稱;在 Microsoft Visual Basic .NET 中,加鎖的做法是 SyncLock GetType(ClassName)。

背景知識:在多線程編程中,lock/SyncLock 語句用於創建代碼中一次只執行一個線程的關鍵部分或簡要部分。(如果您需要同時更新對象中的多個字段,則可能需要該語句 — 您希望確保其他線程不會同時嘗試更新該對象!)此語句將鎖定與您指定的對象相關聯的唯一監視對象,如果其他線程已經鎖定了該監視對象,則等待。一旦它鎖定了監視對象,任何其他線程都無法鎖定該監視對象,除非您的線程解除鎖定,解除鎖定會在封閉塊的結尾自動發生。一種常見的用法是鎖定 this/Me 引用,這樣,只有您的線程可以修改您在使用的對象 — 不過,更好的做法是鎖定您即將修改的特定對象。鎖定儘可能小的對象的好處是可以避免不必要的等待。

GetTypetypeof 返回對該類型的類型對象的引用。System.Type 類型的類型對象包含使您能夠反映類型的方法,這意味着您可以找到它的字段和方法,甚至可以訪問字段和調用方法。一旦您擁有對類型對象的引用,就可以創建該對象的一個實例(並且,如果您使用 Type.GetType shared/static 方法,就可以按名稱獲得對類型對象的引用)。

因此,類型對象非常方便。但是,有些程序員喜歡“濫用”這種方式,藉此來代替可以對其進行加鎖的 static/Shared 對象。(遺憾的是,我們在 C# 文檔和 Visual Basic .NET 文檔中都提到了這種方法,暗示這是一種建議採用的做法。)在這種情況下,這些文檔中的建議是錯誤的(我們會進行糾正)。這種做法是不 可接受的,更不用說建議採用了。

原因是這樣的:由於一個類的所有實例都只有一個類型對象,因此從表面看,鎖定類型對象相當於鎖定類中包含的靜態對象。只要您鎖定類的所有實例,等到其他線程訪問完任一實例的任何部分,然後鎖定訪問,這樣您就可以安全地訪問靜態成員,而不會受到其他線程的干擾。

這種做法的確有效,至少在大多數情況下是這樣的。但它也有一些問題:首先,獲得類型對象實際上是一個很緩慢的過程(儘管大多數程序員會認爲這個過程非常快);其次,任何類中的其他線程、甚至在同一個應用程序域中運行的其他程序都可以訪問該類型對象,因此,它們就有可能代替您鎖定類型對象,完全阻止您的執行,從而導致您掛起。

這裏的基本問題是,您並未擁有該類型對象,並且您不知道還有誰可以訪問它。總的來說,依靠鎖定不是由您創建、並且您不知道還有誰可以訪問的對象是一種很不好的做法。這樣做很容易導致死鎖。最安全的方式就是隻鎖定私有對象。

但除此之外,還有更嚴重的問題。由於在當前版本的 .NET 運行庫中,類型對象有時會在應用程序域之間(但不是在進程之間)共享。(通常這沒有問題,因爲它們是不變的。)這意味着,運行在其他應用程序域(但在同一進程)中的另一個應用程序有可能對您要鎖定的類型對象進行加鎖,並且始終不釋放該類型對象,從而使您的應用程序發生死鎖。並且,這樣可以很容易地獲得類型對象的訪問權限,因爲該對象具有名稱 — 該類型的完全限定名!請記住,lock/SyncLock 會一直阻塞(這是掛起的含蓄說法),直到它可以獲得鎖定爲止。很顯然,依靠鎖定其他程序或組件可以鎖定的對象不是一種很好的做法,並且會導致死鎖。

即使該類型對象在您的應用程序域中是唯一的,這仍然是一種不好的做法,因爲任何代碼都可以訪問公共類型的類型對象,從而導致死鎖的發生。如果您在應用程序中使用的組件不是您編寫的,這種做法尤其成問題。(即使是 lock(this)/SyncLock Me 也可能有這個問題,因爲其他人可能會鎖定您。即使發生了這種事情,問題的根源也可能會比鎖定類型對象而導致的死鎖更容易發現,因爲您的對象並不是跨應用程序域的全局可用對象。)

那麼,應該採用什麼方法呢?非常簡單:只要聲明並創建一個對象作爲鎖,然後使用它而不是 類型對象來進行鎖定。通常,爲了複製問題代碼的語義,您會希望此對象是 static/Shared — 當然,它其實應該是私有的!總之,您可以將以下問題代碼:

// C#
lock(typeof(Foo)) { // BAD CODE! NO! NO! NO!
   // statements;
}

' VB .NET
SyncLock GetType(MyClass) ' BAD CODE! NO! NO! NO!
   ' statements
End SyncLock

更改爲以下正確代碼:

// C#
lock(somePrivateStaticObject) { // Good code!
   // statements;
}

' VB .NET
SyncLock GetType(somePrivateStaticObject) ' Good code!
   ' statements
End SyncLock

當然,您必須已經擁有一個要鎖定的私有靜態對象(如果您使用鎖定來修改靜態對象,實際上您可能已經有了一個!)或者必須創建一個。(使它成爲私有對象可以避免其他類鎖定您的對象。)請不要嘗試鎖定不是引用(對象)類型的字段,例如 int/Integer。那樣會出現編譯器錯誤。如果您沒有要鎖定的私有靜態對象,可能需要創建一個啞對象:

// C#
Class MyClass {
   private static Object somePrivateStaticObject = new Object();
   // methods of class go here--can lock somePrivateStaticObject
}

' VB .NET
Class MyClass
   Private Shared somePrivateStaticObject As New Object
   ' methods of class go here--can lock somePrivateStaticObject
End Class

您需要單獨分析每種情況,以確保不會出現問題,但通常上述技巧會奏效。

有兩點需要注意:首先,類以外的任何代碼都無法鎖定 MyClass.somePrivateStaticObject,因此避免了許多死鎖的可能。由於死鎖屬於那種最難找到根源的問題,因此,避免發生死鎖的可能是一件很好的事情。

其次,您知道,您的應用程序中只有一份 MyClass.somePrivateStaticObject 的副本,並且系統上運行的其他每個應用程序也只有一個副本。因此,在同一個應用程序域中的應用程序之間沒有相互影響。GUI 博士希望您能明白爲什麼修改後的代碼比原來的問題代碼更加可靠和強大。

總之,不要鎖定類型對象,因爲您並不知道哪裏又出現問題了。鎖定類型對象的過程很慢,並且可能發生死鎖情況。這是一種很不好的編程習慣。相反,您應該在對象中鎖定靜態對象。

致謝!

GUI 博士在此感謝 .NET 運行庫的性能設計師 Rico Mariani 提供了這方面的寶貴意見。

GUI 博士請教

著名的問題解決專家“GUI 博士”很高興在 Internet 和基於 Windows 的開發方面提供百科全書式的知識,使各地的開發人員從中獲益。如果遇到無法解決的問題,請將您的疑問發送到 [email protected]。雖然博士忙碌的工作安排使他無法回覆所有的問題,但他會盡可能地在這裏回答更多的問題。如果恰好選中了您的問題,那麼博士會送您一件 GUI 博士 T 恤衫!(請注意:問題可能經過整理,以確保語法正確,邏輯清晰。)

GUI 博士提供的知識還不夠多?請閱讀 GUI .NET 博士專欄文章!

有關博士提供的其他知識,請閱讀 MSDN 庫中每月發佈兩次的 Dr. GUI .NET 專欄文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章