SpinWait


其實SpinWait的code 非常簡單,以前看過很多遍,但是從來都沒有整理過,整理也是再次學習吧。

我們先看看SpinWait的一些評論或者注意點吧:如果等待某個條件滿足需要的時間很短,而且不希望發生昂貴的上下文切換,那么基於自旋的等待是一種很好的替換方案,SpinWait不僅提供了基本自旋功能,而且還提供了SpinWait.SpinUntil方法,使用這個方法能夠自旋直到滿足某個條件為止,此外SpinWait是一個Struct,從內存的角度上說,開銷很小。SpinLock是對SpinWait的簡單封裝。需要注意的是:長時間的自旋不是很好的做法,因為自旋會阻塞更高級的線程及其相關的任務,還會阻塞垃圾回收機制。SpinWait並沒有設計為讓多個任務或線程並發使用,因此多個任務或線程通過SpinWait方法進行自旋,那么每一個任務或線程都應該使用自己的SpinWait實例。當一個線程自旋時,會將一個內核放入到一個繁忙的循環中,而不會讓出當前處理器時間片剩余部分,當一個任務或者線程調用Thread.Sleep方法時,底層線程會讓出當前處理器時間片的剩余部分,這是一個大開銷的操作。因此,在大部分情況下, 不要在循環內調用Thread.Sleep方法等待特定的條件滿足 。

其實現如下:

[HostProtection(Synchronization = true, ExternalThreading = true)]
public struct SpinWait
{
internal const int YIELD_THRESHOLD = 10; // When to switch over to a true yield.
internal const int SLEEP_0_EVERY_HOW_MANY_TIMES = 5; // After how many yields should we Sleep(0)?
internal const int SLEEP_1_EVERY_HOW_MANY_TIMES = 20; // After how many yields should we Sleep(1)?

// The number of times we've spun already.
private int m_count;
public bool NextSpinWillYield
{
get { return m_count > YIELD_THRESHOLD || PlatformHelper.IsSingleProcessor; }
}
public void SpinOnce()
{
if (NextSpinWillYield)
{
int yieldsSoFar = (m_count >= YIELD_THRESHOLD ? m_count - YIELD_THRESHOLD : m_count);
if ((yieldsSoFar % SLEEP_1_EVERY_HOW_MANY_TIMES) == (SLEEP_1_EVERY_HOW_MANY_TIMES - 1))
{
Thread.Sleep(1);
}
else if ((yieldsSoFar % SLEEP_0_EVERY_HOW_MANY_TIMES) == (SLEEP_0_EVERY_HOW_MANY_TIMES - 1))
{
Thread.Sleep(0);
}
else
{
Thread.Yield();
}
}
else
{
Thread.SpinWait(4 << m_count);
}
// Finally, increment our spin counter.
m_count = (m_count == int.MaxValue ? YIELD_THRESHOLD : m_count + 1);
}

public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout)
{
if (millisecondsTimeout < Timeout.Infinite)
{
throw new ArgumentOutOfRangeException("millisecondsTimeout", millisecondsTimeout, Environment.GetResourceString("SpinWait_SpinUntil_TimeoutWrong"));
}
if (condition == null)
{
throw new ArgumentNullException("condition", Environment.GetResourceString("SpinWait_SpinUntil_ArgumentNull"));
}
uint startTime = 0;
if (millisecondsTimeout != 0 && millisecondsTimeout != Timeout.Infinite)
{
startTime = TimeoutHelper.GetTime();
}
SpinWait spinner = new SpinWait();
while (!condition())
{
if (millisecondsTimeout == 0)
{
return false;
}
spinner.SpinOnce();
if (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield)
{
if (millisecondsTimeout <= (TimeoutHelper.GetTime() - startTime))
{
return false;
}
}
}
return true;
}
public void Reset()
{
m_count = 0;
}
}
SpinWait的核心方是SpinOnce方法,假如我們是多核CPU,該方法在前10次【NextSpinWillYield為false】SpinOnce調用Thread.SpinWait【這個是一個win32的方法 , private static extern void SpinWaitInternal(int iterations);】,后面的調用主要是調用 Thread.Yield()【Win32API, private static extern bool YieldInternal()】,當count是14的時候第一次調用  Thread.Sleep(0),當count是29的時候第一次調用 Thread.Sleep(1),后的的規則是(count-10)%20==19 調用Thread.Sleep(1),否者檢查(count-10)%5==4Thread.Sleep(0).

SpinUntil方法的實現句非常簡單了,就是循環調用SpinOnce方法,如果條件滿足 或者超時 則退出循環。


Thread.Yeild
該方法是在 .Net 4.0 中推出的新方法,它對應的底層方法是 SwitchToThread。Yield 的中文翻譯為 “放棄”,這里意思是主動放棄當前線程的時間片,並讓操作系統調度其它就緒態的線程使用一個時間片。但是如果調用 Yield,只是把當前線程放入到就緒隊列中,而不是阻塞隊列。如果沒有找到其它就緒態的線程,則當前線程繼續運行。
優勢:比 Thread.Sleep(0) 速度要快,可以讓低於當前優先級的線程得以運行。可以通過返回值判斷是否成功調度了其它線程。
劣勢:只能調度同一個處理器的線程,不能調度其它處理器的線程。當沒有其它就緒的線程,會一直占用 CPU 時間片,造成 CPU 100%占用率

Thread.Sleep(0)
Sleep 的意思是告訴操作系統自己要休息 n 毫秒,這段時間就讓給另一個就緒的線程吧。當 n=0 的時候,意思是要放棄自己剩下的時間片,但是仍然是就緒狀態,其實意思和 Yield 有點類似。但是 Sleep(0) 只允許那些優先級相等或更高的線程使用當前的CPU,其它線程只能等着挨餓了。如果沒有合適的線程,那當前線程會重新使用 CPU 時間片。
優勢:相比 Yield,可以調度任何處理器的線程使用時間片。
劣勢:只能調度優先級相等或更高的線程,意味着優先級低的線程很難獲得時間片,很可能永遠都調用不到。當沒有符合條件的線程,會一直占用 CPU 時間片,造成 CPU 100%占用率。

Thread.Sleep(1)
該方法使用 1 作為參數,這會強制當前線程放棄剩下的時間片,並休息 1 毫秒(因為不是實時操作系統,時間無法保證精確,一般可能會滯后幾毫秒或一個時間片)。但因此的好處是,所有其它就緒狀態的線程都有機會競爭時間片,而不用在乎優先級。
優勢:可以調度任何處理器的線程使用時間片。無論有沒有符合的線程,都會放棄 CPU 時間,因此 CPU 占用率較低。
劣勢:相比 Thread.Sleep(0),因為至少會休息一定時間,所以速度要更慢。

Thread.Sleep(0) vs Sleep(1) vs Yeild


---------------------
作者:dz45693
來源:CSDN
原文:https://blog.csdn.net/ma_jiang/article/details/78623306
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM