2016-04-14 36 views
6

這會消耗接近零的CPU(酷睿i5系列)C#SpinWait長期等待

public void SpinWait() { 

     for (int i = 0; i < 10000; i++) 
     { 
      Task.Factory.StartNew(() => 
      { 
       var sw = new SpinWait(); 
       while (true) 
       { 
        sw.SpinOnce(); 
       } 
      }); 
     } 
    } 

在我的代碼的性能差異比較SemaphoreSlim是的情況下3倍倍以上旋轉時真的是有道理的(5 MOPS)。但是,我擔心使用它進行長期等待。標準的建議是實施兩階段等待操作。我可以檢查NextSpinWillYield屬性,並引入一個計數器+重置來增加默認的旋轉迭代而不屈服,並且退回到信號量。

但長期等待使用SpinWait.SpinOnce的缺點是什麼?我已瀏覽了其implementation,並在需要時正確收益。它使用Thread.SpinWait,在現代CPU上使用PAUSE指令,根據Intel,它非常高效。

我在監視任務管理器時發現的一個問題是,如果由於默認的ThreadPool算法逐漸增加(當所有任務繁忙時它每秒都會添加一個線程),線程數量會逐漸增加。這可以通過使用ThreadPool.SetMaxThreads來解決,然後線程的數量是固定的,並且CPU使用率仍然接近零。

如果長期等待任務的數量是有限的,那麼使用SpinWait.SpinOnce進行長期等待還有什麼其他缺陷。它依賴於CPU系列,操作系統,.NET版本嗎?

(只是爲了澄清:我還是會執行兩階段的等待,我只是珍玩爲什麼不使用SpinOnce所有的時間)

回答

9

好了,下側正是你看到的人,你的代碼正在佔領一條線而不做任何事情。阻止其他代碼運行並強制線程池管理器執行某些操作。修補ThreadPool.SetMaxThreads()只是一種可能是大量流血傷口的創可貼,只有在需要搭上飛機回家時才使用它。

紡紗應該只有當你有很好的保證,這樣做是更多有效的比線程上下文切換試圖只有嘗試。這意味着您必須確保線程可以在約10,000 cpu週期或更少的時間內繼續運行。這只是5 微秒,給出或採取一個比大多數程序員認爲「長期」更低的整數。

使用將觸發線程上下文切換的同步對象。或者lock關鍵字。

不僅會產生處理器,所以其他等待的線程可以完成他們的工作,從而完成更多的工作,它還爲OS線程調度程序提供了一個很好的線索。發送信號的同步對象會影響線程的優先級,因此很可能會接下來處理器。

+0

謝謝!順便說一下,多少微秒需要線程喚醒,例如如果我等着'semaphore.WaitAsync(-1,token)'?如果我使用'Sleep(1)'進行spinwait和'SpinOne'產生,那麼在最壞的情況下,如果一個信號在調用'Sleep(1)'後到達,至少會引入'15毫秒的延遲。如果我改用信號量,那麼線程在信號量喚醒後的最壞情況下的延遲是什麼?釋放()'? –

+0

這很大程度上取決於機器上還發生了什麼。當然,您還必須與其他進程和內核線程擁有的線程競爭。它需要幾微秒,而不是幾毫秒。只需用秒錶來測量它,由於偶爾的最壞情況可能會很長,所以一定要採取多次測量的中位數。請記住,你無法做任何事情,所以它只是信息。切勿使用Sleep()。 –

+0

因此,信號量釋放之後醒來與.NET無關,使用C/C++甚至程序集都無濟於事? (不會使用它們,只是爲了解這是硬件/操作系統設計) –