2010-10-04 45 views
7

當您將鎖定操作與鎖定()(和其他更高級別的鎖定)混合時,是否保證原子讀取?聯鎖類可以安全地與lock()混合嗎?

我感興趣的是混合類似這樣的鎖定機制時的一般行爲,以及Int32和Int64之間的任何差異。

private Int64 count; 
private object _myLock; 

public Int64 Count 
{ 
    get 
    { 
    lock(_myLock) 
    { 
     return count; 
    } 
    } 
} 

public void Increment 
{ 
    Interlocked.Increment(ref count); 
} 
+1

切勿鎖定'this',該鎖將與類鎖定對象之外的鎖相沖突。只鎖定私人引用類型的成員。如果你沒有一個適當的私有引用類型的成員,而不是正確類型的靜態或實例類型的成員,那麼就應該爲此目的設置'private object _myLock'''或'private static object _myStaticLock''。 – 2010-10-04 14:27:17

+0

@Jon,注意和編輯 – 2010-10-04 14:30:13

+3

我仍然有這樣的感覺,你可能會有比這個問題更大的魚來炒。就這個問題而言,我很滿意我的答案,但它並沒有處理線程A基於count進行某些操作的事情,而線程B更改了計數,因爲這是否是一個問題,以及如何處理它如果是,取決於你比這裏更多。 – 2010-10-04 14:37:52

回答

9

注意:此問題已被更改爲count。對於當前的問題,而不是Thread.VolatileRead我會用Interlocked.Read,其中也有揮發性的語義,還將處理這裏討論和引入

原子的讀是沒有鎖定保障問題的64位讀的問題,因爲讀的正確對齊的32位或更小,這你count是,保證是原子的值。

這與64位值不同,如果它從-1開始,並在另一個線程將其遞增時讀取,則可能導致讀取值爲-1(在增量之前發生),0(在增量之後發生)或者4294967295或-4294967296(32位寫入0,其他32位等待寫入)。

原子增量Interlocked.Increment表示整個增量操作是原子的。考慮增量是概念上的:

  1. 閱讀值。
  2. 添加一個值。
  3. 寫入數值。

然後,如果x是54和一個線程試圖增加它而另一嘗試將其設置爲67,這兩個正確可能的值是67(增量首先發生,然後被寫入結束)或68(分配發生第一,然後遞增),但非原子增量可能導致55(增量讀取,分配67發生,遞增寫入)。

更常見的真實情況是x是54,一個線程遞增,另一個遞減。這裏唯一有效的結果是54(一個向上,然後一個向下,反之亦然),但如果不是原子的,那麼可能的結果是53,54和55.

如果你只是想要一個以原子方式遞增的計數,正確的代碼是:

private int count; 

public int Count 
{ 
    get 
    { 
    return Thread.VolatileRead(byref count); 
    } 
} 

public void Increment 
{ 
    Interlocked.Increment(count); 
} 

如果不管你想採取什麼行動,那麼它需要更強的鎖定。這是因爲使用計數的線程在其操作完成之前可能會過時。在這種情況下,您需要鎖定所有關心計數以及改變計數的所有內容。究竟需要如何完成(以及它的重要性如何)取決於你的用例的更多事項,而不是從你的問題中推斷出來。

編輯:哦,你可能想鎖定只是爲了強制內存屏障。如果要刪除鎖,您可能還想將Count的實現更改爲return Thread.VolatileRead(ref count);以確保CPU緩存已刷新。這取決於在這種情況下緩存過期的重要性。 (另一種選擇是使count易失性,因爲所有的讀寫操作都是不穩定的,請注意,Interlocked操作不需要這些操作,因爲它們總是不穩定的。)

編輯2:的確,你是如此可能想要這種不穩定的閱讀,我正在改變上面的答案。它可能你不會在意它提供什麼,但不太可能。

+0

在這種情況下不應該計數被宣佈爲volatile嗎?否則,你最終將註冊兌現問題,不是嗎? – 2010-10-04 14:44:38

+0

@Martin。 GMTA,正如你所評論的那樣補充說。雖然如此,只是我們想要的將取決於一些事情。 VolatileRead將服務於問題中的內容,但是一旦添加了其他操作,「volatile」(根據您的建議)會更好。值得補充的是,Interlocked API具有正確的內存屏障,因此不需要對其進行波動(甚至在您通過volatile字段調用byref時獲得的警告文檔中也會這樣說)。 – 2010-10-04 14:51:46

+0

並且對那些詢問http://stackoverflow.com/questions/3853678/why-lock-is-needed-to-implement-a-readonly-int-property/的人表示讚賞,因爲它在考慮我對此的回答,我意識到它也適用於這裏。 – 2010-10-04 15:01:12

3

提供給lock關鍵字的參數必須是基於引用類型的對象。在你的代碼中,這是值類型,這可能意味着裝箱,這使得鎖定語句無用。

聯鎖操作相對於另一個聯鎖操作在另一個線程中運行,並應用於同一個變量。沒有線程安全性,如果一個線程使用互鎖操作,另一個使用另一個同步算法或不同步更改同一個變量。

+0

固定代碼,對不起,關於 – 2010-10-04 14:08:31

2

你的問題的答案是否定的。 lockInterlocked與彼此沒有任何關係,並且它們不以您建議的方式一起工作。

此外,不知道你的代碼是怎麼回事。它不會編譯。您無法鎖定值類型。另外,Increment()需要ref參數。

+0

固定代碼,對不起, – 2010-10-04 14:08:24

+0

是的,至少現在會編譯。但是肯定的是,這兩種類型的鎖不會相互同步。此外,你不應該鎖定這一點,但這是一個完全不同的討論。 – Bryan 2010-10-04 14:11:44

+0

現在我想到了,你根本不需要鎖上房子。返回一個int是一個原子操作。如果這是你的全部,你可以刪除鎖(這),你不會有問題。 – Bryan 2010-10-04 14:18:30

2

首先,您不能鎖定值類型,如int。

當你鎖定一個值類型時,它首先被裝箱到一個對象中。問題在於它每次都會裝盒,每次都會出現不同的「盒子」。每次您都會鎖定不同的對象,導致鎖定塊無用。

問題放在一邊,讓我們說你你鎖定一個引用類型,並使用Interlocked類在另一個線程。線程之間不會有同步。當你想同步你必須在兩個線程上使用相同的機制。

+0

固定代碼,對不起有關 – 2010-10-04 14:09:21

3

是當你用鎖()(和其他更高級別的鎖)混合交錯操作的原子讀取保障?

原子的讀的INT總是保證無論任何形式的鎖定。整型的讀取C#的規範狀態是總是原子。

我認爲你的實際問題比你問的一個不同。你能澄清這個問題嗎?

+0

我打算問一個關於混合隱喻的更一般的問題,編輯相應 – 2010-10-04 14:29:03

相關問題