2012-04-25 52 views
9

我有一個線程,直到由另一個線程更改的int是一個特定的值。我是否需要此字段是易變的?

int cur = this.m_cur; 
while (cur > this.Max) 
{ 
    // spin until cur is <= max 
    cur = this.m_cur; 
} 

是否需要將this.m_cur聲明爲volatile才能使其工作?由於編譯器優化,這可能會永久旋轉嗎?

+9

將int設爲屬性,並在setter方法中指示線程(autoResetEvent,也許)。繞過問題,CPU使用減少,揮發性疑問消除。 – 2012-04-25 00:27:31

+0

除少數情況外,這通常是一個糟糕的主意;你碰巧知道你期望旋轉多少微秒? – 2012-04-25 06:24:13

+0

如果在setter線程寫入超出限制值時輪詢線程未運行,讀取由另一個線程寫入的'cur'時的CPU循環將無法檢測到超出限制。如果它已經被超載盒子搶佔,那麼在檢測到超出範圍之前,它將不得不等待平均半量子。如果在輪詢器未運行時cur再次回到範圍內,則根本不會檢測到超出範圍的條件。 – 2012-04-25 08:46:59

回答

12

是的,這是一個很難的要求。即時編譯器允許將m_cur的值存儲在處理器寄存器中,而無需從內存中刷新它。 x86抖動實際上是這樣做的,x64抖動不會(至少在我最後一次看到它的時候)。

易失性關鍵字是必需的來抑制此優化。

易失性意味着Itanium內核完全不同,這是一款內存模型較弱的處理器。不幸的是,這就是它進入MSDN庫和C#語言規範。在ARM內核上意味着什麼還有待觀察。

+4

我喜歡你如何回答他的問題,而不是像許多人喜歡的那樣只是說'做另一種方式'。根據他的需要,信號可能效率更高,但他的問題不是「......還是更好?」此外,我敢肯定,許多使用在這裏學到了新東西。謝謝。 – payo 2012-04-25 01:17:59

+0

+1:順便說一下,我們確定屬性會阻止JIT編譯器進行提升優化。該屬性很簡單,所以它可能首先被內聯。我想可以做一個2階段優化:內聯提升。我在規格中沒有看到任何會妨礙這一點的規格,但也許我沒有看到那麼難。 – 2012-04-25 02:00:10

+1

@布萊恩 - 我發現它困惑的屬性被用於需要揮發性的情況下,但沒有任何同步。 BackgroundWorker.CancellationPending是一個很好的例子,bool。這在我所知的任何地方都沒有描述,* volatile *的語義記錄非常糟糕。在.NET之前,他們一直都是。 – 2012-04-25 02:12:50

4

下面的博客在c#中的內存模型有一些迷人的細節。總之,使用volatile關鍵字似乎更安全。

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

從下面

class Test 
{ 
    private bool _loop = true; 

    public static void Main() 
    { 
     Test test1 = new Test(); 

     // Set _loop to false on another thread 
     new Thread(() => { test1._loop = false;}).Start(); 

     // Poll the _loop field until it is set to false 
     while (test1._loop == true) ; 

     // The loop above will never terminate! 
    } 
} 

的博客有兩種可能的方式來獲得,而循環終止:使用 鎖來保護所有的訪問(讀取和寫入)到_loop字段 將_loop字段標記爲易失性讀取非易失性字段可能會觀察到陳舊值:編譯器優化 和處理器優化有兩個原因。

+1

根本不使用輪詢循環似乎更安全。 – 2012-04-25 00:40:15

+0

對於大多數情況是正確的。雖然我會想象也有旋轉鎖的理由,例如避免產生線程的上下文切換。 – 2012-04-25 00:49:46

+0

上下文切換的麻煩在於它並不總是可能的。另外,OP代碼不是clsssic布爾標誌自旋鎖。如果由於存在比內核多的就緒線程而導致該框過載,則輪詢線程可能會被搶佔並且不會運行一段時間。在此期間,它無法檢測到'超過'限制。如果在輪詢器運行之前cur超出限制範圍,則超出限制的條件將完全丟失。 – 2012-04-25 08:56:42

0

這取決於如何修改m_cur。如果它使用的是正常的賦值語句,如m_cur--;,那麼它確實需要變化。但是,如果使用Interlocked操作之一修改它,則不會因爲Interlocked的方法自動插入內存屏障來確保所有線程都獲得備忘錄。

一般來說,使用Interlocked來修改線程之間共享的原子值是更好的選擇。它不僅爲您處理內存障礙,而且它往往比其他同步選項快一點。

也就是說,像其他人說的那樣,輪詢循環非常浪費。暫停需要等待的線程會更好,並且讓正在修改m_cur的人負責在時間到來時將其喚醒。取決於您的具體需求,Monitor.Wait() and Monitor.Pulse()AutoResetEvent可能非常適合該任務。

+0

無論如何,輪詢循環解決方案無法正常工作,至少不是超載的框。 setter線程可以在輪詢器未運行時寫入超出範圍的'cur'。如果'cur'在輪詢器運行之前再次回到範圍內,則根本不會檢測到超出範圍的條件。 – 2012-04-25 09:08:36