我有一個線程,直到由另一個線程更改的int是一個特定的值。我是否需要此字段是易變的?
int cur = this.m_cur;
while (cur > this.Max)
{
// spin until cur is <= max
cur = this.m_cur;
}
是否需要將this.m_cur聲明爲volatile才能使其工作?由於編譯器優化,這可能會永久旋轉嗎?
我有一個線程,直到由另一個線程更改的int是一個特定的值。我是否需要此字段是易變的?
int cur = this.m_cur;
while (cur > this.Max)
{
// spin until cur is <= max
cur = this.m_cur;
}
是否需要將this.m_cur聲明爲volatile才能使其工作?由於編譯器優化,這可能會永久旋轉嗎?
是的,這是一個很難的要求。即時編譯器允許將m_cur的值存儲在處理器寄存器中,而無需從內存中刷新它。 x86抖動實際上是這樣做的,x64抖動不會(至少在我最後一次看到它的時候)。
易失性關鍵字是必需的來抑制此優化。
易失性意味着Itanium內核完全不同,這是一款內存模型較弱的處理器。不幸的是,這就是它進入MSDN庫和C#語言規範。在ARM內核上意味着什麼還有待觀察。
我喜歡你如何回答他的問題,而不是像許多人喜歡的那樣只是說'做另一種方式'。根據他的需要,信號可能效率更高,但他的問題不是「......還是更好?」此外,我敢肯定,許多使用在這裏學到了新東西。謝謝。 – payo 2012-04-25 01:17:59
+1:順便說一下,我們確定屬性會阻止JIT編譯器進行提升優化。該屬性很簡單,所以它可能首先被內聯。我想可以做一個2階段優化:內聯提升。我在規格中沒有看到任何會妨礙這一點的規格,但也許我沒有看到那麼難。 – 2012-04-25 02:00:10
@布萊恩 - 我發現它困惑的屬性被用於需要揮發性的情況下,但沒有任何同步。 BackgroundWorker.CancellationPending是一個很好的例子,bool。這在我所知的任何地方都沒有描述,* volatile *的語義記錄非常糟糕。在.NET之前,他們一直都是。 – 2012-04-25 02:12:50
下面的博客在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字段標記爲易失性讀取非易失性字段可能會觀察到陳舊值:編譯器優化 和處理器優化有兩個原因。
根本不使用輪詢循環似乎更安全。 – 2012-04-25 00:40:15
對於大多數情況是正確的。雖然我會想象也有旋轉鎖的理由,例如避免產生線程的上下文切換。 – 2012-04-25 00:49:46
上下文切換的麻煩在於它並不總是可能的。另外,OP代碼不是clsssic布爾標誌自旋鎖。如果由於存在比內核多的就緒線程而導致該框過載,則輪詢線程可能會被搶佔並且不會運行一段時間。在此期間,它無法檢測到'超過'限制。如果在輪詢器運行之前cur超出限制範圍,則超出限制的條件將完全丟失。 – 2012-04-25 08:56:42
這取決於如何修改m_cur。如果它使用的是正常的賦值語句,如m_cur--;
,那麼它確實需要變化。但是,如果使用Interlocked操作之一修改它,則不會因爲Interlocked的方法自動插入內存屏障來確保所有線程都獲得備忘錄。
一般來說,使用Interlocked來修改線程之間共享的原子值是更好的選擇。它不僅爲您處理內存障礙,而且它往往比其他同步選項快一點。
也就是說,像其他人說的那樣,輪詢循環非常浪費。暫停需要等待的線程會更好,並且讓正在修改m_cur的人負責在時間到來時將其喚醒。取決於您的具體需求,Monitor.Wait() and Monitor.Pulse()和AutoResetEvent可能非常適合該任務。
無論如何,輪詢循環解決方案無法正常工作,至少不是超載的框。 setter線程可以在輪詢器未運行時寫入超出範圍的'cur'。如果'cur'在輪詢器運行之前再次回到範圍內,則根本不會檢測到超出範圍的條件。 – 2012-04-25 09:08:36
將int設爲屬性,並在setter方法中指示線程(autoResetEvent,也許)。繞過問題,CPU使用減少,揮發性疑問消除。 – 2012-04-25 00:27:31
除少數情況外,這通常是一個糟糕的主意;你碰巧知道你期望旋轉多少微秒? – 2012-04-25 06:24:13
如果在setter線程寫入超出限制值時輪詢線程未運行,讀取由另一個線程寫入的'cur'時的CPU循環將無法檢測到超出限制。如果它已經被超載盒子搶佔,那麼在檢測到超出範圍之前,它將不得不等待平均半量子。如果在輪詢器未運行時cur再次回到範圍內,則根本不會檢測到超出範圍的條件。 – 2012-04-25 08:46:59