2012-08-14 48 views
12

我有一個工作線程中的對象,我可以指示停止運行。我可以實現這個用布爾或的AutoResetEvent:AutoResetEvent與布爾值來停止線程

布爾:

private volatile bool _isRunning; 

public void Run() { 
    while (_isRunning) 
    { 
     doWork(); 
     Thread.Sleep(1000); 
    } 
} 

的AutoResetEvent:

private AutoResetEvent _stop; 

public void Run() { 
    do { 
     doWork(); 
    } while (!_stop.WaitOne(1000)); 
} 

然後Stop()方法將設置_isRunning爲false,或致電_stop.Set()

除此之外,AutoResetEvent的解決方案可能會稍微停止一點,這些方法之間有什麼區別嗎?這個比那個好嗎?

+2

我會使用一個計時器,而不是睡覺。要結束它,你可以停止計時器。 – Servy 2012-08-14 13:29:21

+0

我已經添加了一些觀察到可能的答案,基本上我不認爲這些片段做你想要的。 – Jodrell 2012-08-14 14:20:34

回答

11

C#volatile不提供所有擔保。它可能仍舊讀取陳舊的數據。更好地使用底層操作系統同步機制,因爲它提供了更強大的保證。

這一切都是由Eric利珀特(確實值得一讀),大深度discussed,這裏是短報價:

在C#中,「揮發性」不僅意味着「確保編譯器和 抖動請勿對此變量執行任何代碼重新排序或註冊緩存優化。這也意味着「告訴處理器 做任何他們需要做的事情來確保我正在讀取最新值,即使這意味着停止其他處理器並使它們與主存儲器同步其高速緩存」「。

其實,最後一點是謊言。 volatile的真實語義讀取 和寫入比我在這裏概述的要複雜得多;在 事實它們實際上並不能保證每個處理器都停止它在 正在做的事情並更新到/從主內存的高速緩存。相反,他們提供了關於在讀取之前和之後如何存儲器訪問的較弱保證,並且可以觀察到寫入相對於彼此是有序的。 某些操作(如創建新線程,輸入鎖或使用Interlocked系列方法之一)會引入更強大的關於排序觀察的保證。如果您需要更多詳細信息,請參閱 閱讀C#4.0規範的第3.10和10.5.3節。

坦率地說,我勸阻你永遠做一個不穩定的領域。易變的 字段表示您正在做一些徹頭徹尾的瘋狂操作:您是 嘗試在兩個不同線程 上讀取和寫入相同的值,而沒有鎖定就位。鎖確保內存讀取或在鎖內修改內存被認爲是一致的,鎖保證 只有一個線程一次訪問給定的內存塊,因此 。

+0

+1,因爲這更詳細地重申了我的第一點 – Jodrell 2012-08-14 14:23:28

+0

這裏的易失性工作很好。任何寫入其中的線程都將其所有內存更新推送到主內存。任何讀取的線程都會從main中刷新其工作內存。因此,一個volatile的_write_(以及它所引用的數據)將被來自該volatile的任何_read_看到。也就是說,在這種情況下,AutoResetEvent看起來好多了。 – RalphChapin 2012-08-14 17:14:54

+1

@RalphChapin在多處理器系統上AFAIK只有*弱*保證所有處理器都會看到更新的值。 – oleksii 2012-08-14 17:24:45

3

恕我直言AutoResetEvent更好,因爲在這種情況下,您無法忘記關鍵字volatile

0

這取決於如果上面的代碼片段是你所做的或不是。正如其名稱所示,AutoReset事件在WaitOne傳遞後重置。這意味着你可以直接使用它,而不是使用布爾,必須將其設置回真。

1

在使用volatile關鍵字之前,您應該閱讀this,我想,在研究多線程時,您可以閱讀整篇http://www.albahari.com/threading/文章。

它解釋了volatile關鍵字的微妙之處以及它的行爲可能會出乎意料的原因。


你會注意到,在使用時volatile,讀取和寫入可以得到重新排序,這可能導致在密切併發情況下的額外的迭代。在這種情況下,您可能需要等待一秒左右。


看後,我不認爲你的代碼工作有以下幾個原因,

的「布爾」片段總是睡約一秒鐘,可能不是你想要的。

「AutoResetEvent:」snippet doesen沒有實例化_stop,並且總是至少運行一次doWork()

+0

我在_stop.WaitOne()上忘了'!'。這些例子有點簡單,但基本上我想定期運行'doWork()'。 「DoWork()」會運行直到它完成工作,之後它必須等待一段時間才能積累新的工作。 – Sjoerd 2012-08-14 14:33:38

+0

@Sjoerd,我編輯了我的最後一部分。圍繞「volatile」的警告仍然是有關的。 – Jodrell 2012-08-14 14:42:22

6

揮發性不夠好,但實際上它會一直工作,因爲操作系統調度程序總是會最終鎖定。並且可以在一個強大的內存模型的核心上運行良好,比如x86,它會在內核之間保持高速緩存的同步。

所以真正重要的是線程將多快響應停止請求。測量起來很簡單,只需在控制線程中啓動一個秒錶,並記錄工作線程中while循環之後的時間。結果我從重複服用1000個樣本和取平均測量,重複10次:

volatile bool, x86:   550 nanoseconds 
volatile bool, x64:   550 nanoseconds 
ManualResetEvent, x86:  2270 nanoseconds 
ManualResetEvent, x64:  2250 nanoseconds 
AutoResetEvent, x86:  2500 nanoseconds 
AutoResetEvent, x64:  2100 nanoseconds 
ManualResetEventSlim, x86: 650 nanoseconds 
ManualResetEventSlim, x64: 630 nanoseconds 

要注意的是對於揮發性布爾結果是非常不可能的外觀,以及在處理器上帶有弱存儲器模型,如ARM或Itanium。我沒有一個測試。

顯然它看起來像你想支持ManualResetEventSlim,給予良好的性能和保證。

一個注意到這些結果,他們測量工作線程運行一個熱循環,不斷測試停止條件,並沒有做任何其他工作。這與真實代碼不太匹配,線程通常不會檢查經常停止的情況。這使得這些技術之間的差異在很大程度上是無關緊要的。