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

  本文將要提到的線程及其相關內容,均是指 Windows 操做系統中的線程,不涉及其它操做系統。算法

 

  文章索引windows

 

  在進入正文前,有幾個知識點須要你們在閱讀前有所瞭解。多線程

 

核心概念

  優先級調度算法工具

  處理器是一個操做系統執行任務的工具,線程是一個操做系統執行任務的基本單位,處理器的數量決定了不可能全部線程都能同時獲得執行。這就須要經過某種算法來進行任務高度。而 Windows 是一個搶佔式的多任務操做系統,咱們來看下維基百科對於搶佔式的定義:oop

In computing, preemption is the act of temporarily interrupting a task being carried out by a computer system, without requiring its cooperation, and with the intention of resuming the task at a later time. Such a change is known as a context switch. It is normally carried out by a privileged task or part of the system known as a preemptive scheduler, which has the power to preempt, or interrupt, and later resume, other tasks in the system.post

--- Preemption (computing)測試

  上面這段英文意味着在搶佔式的操做系統中,執行任務的多個線程之間會由於某種因素互相爭搶在處理器上運行的機會。而這種因素就是標題所說的 「優先級」。優化

 

線程是根據其優先級而調度執行的。 即便線程正在運行時中執行,全部線程都是由操做系統分配處理器時間片的。 用於肯定線程執行順序的調度算法的詳細狀況隨每一個操做系統的不一樣而不一樣。ui

在某些操做系統下,具備最高優先級(相對於可執行線程而言)的線程通過調度後老是首先運行。 若是具備相同優先級的多個線程均可用,則計劃程序將遍歷處於該優先級的線程,併爲每一個線程提供一個固定的時間片(段)來執行。 只要具備較高優先級的線程能夠運行,具備較低優先級的線程就不會執行。 若是在給定的優先級上再也不有可運行的線程,則計劃程序將移到下一個較低的優先級並在該優先級上調度線程以執行。 若是此時具備較高優先級的線程能夠運行,則具備較低優先級的線程將被搶先,並容許具備較高優先級的線程再次執行。 除此以外,當應用程序的用戶界面在前臺和後臺之間移動時,操做系統還能夠動態調整線程優先級。 其餘操做系統能夠選擇使用不一樣的調度算法。this

--- 調度線程

  上面文字中提到優先級能夠由操做系統動態調整。Windows 除了會在先後臺切換的時候調整優先級還會爲 I/O 操做動態提高優先級,或者使用 「飢渴」 的時間片分配策略來動態調整,若是有線程一直渴望獲得時間片可是很長時間都沒有得到時間片,Windows 就會臨時將這個線程的優先級提升,並一次分配給2倍的時間片來執行,當用完2倍的時間片後,優先級又會恢復到以前的水平。

 

  線程運行狀態

 

  一個線程從開始到終止可能會有上述幾種狀態,這幾種狀態能夠互相轉換。(上圖中的 「就緒」 指的是 runnable 狀態,又稱爲 「ready to run」 狀態,我的感受翻譯成 「就緒」 比 「可運行」 要來得明白)。

 

Thread.Yeild

  對上述概念有所瞭解後,我將正式介紹這三個方法的區別。

 

  該方法是在 .Net 4.0 中推出的新方法,它對應的底層方法是 SwitchToThread。

  Yield 的中文翻譯爲 「放棄」,這裏意思是主動放棄當前線程的時間片,並讓操做系統調度其它就緒態的線程使用一個時間片。可是若是調用 Yield,只是把當前線程放入到就緒隊列中,而不是阻塞隊列。若是沒有找到其它就緒態的線程,則當前線程繼續運行。

Yielding is limited to the processor that is executing the calling thread. The operating system will not switch execution to another processor, even if that processor is idle or is running a thread of lower priority. If there are no other threads that are ready to execute on the current processor, the operating system does not yield execution, and this method returns false. 

This method is equivalent to using platform invoke to call the native Win32 SwitchToThread function. You should call the Yield method instead of using platform invoke, because platform invoke bypasses any custom threading behavior the host has requested.

--- Thread.Yield Method

 

  優點:比 Thread.Sleep(0) 速度要快,可讓低於當前優先級的線程得以運行。能夠經過返回值判斷是否成功調度了其它線程。

  劣勢:只能調度同一個處理器的線程,不能調度其它處理器的線程。當沒有其它就緒的線程,會一直佔用 CPU 時間片,形成 CPU 100%佔用率。

 

 

Thread.Sleep(0)

  Sleep 的意思是告訴操做系統本身要休息 n 毫秒,這段時間就讓給另外一個就緒的線程吧。當 n=0 的時候,意思是要放棄本身剩下的時間片,可是仍然是就緒狀態,其實意思和 Yield 有點相似。可是 Sleep(0) 只容許那些優先級相等或更高的線程使用當前的CPU,其它線程只能等着捱餓了。若是沒有合適的線程,那當前線程會從新使用 CPU 時間片。

 

If you specify 0 milliseconds, the thread will relinquish the remainder of its time slice but remain ready.

--- Sleep Function

 

  優點:相比 Yield,能夠調度任何處理器的線程使用時間片。

  劣勢:只能調度優先級相等或更高的線程,意味着優先級低的線程很難得到時間片,極可能永遠都調用不到。當沒有符合條件的線程,會一直佔用 CPU 時間片,形成 CPU 100%佔用率。

 

Thread.Sleep(1)

   該方法使用 1 做爲參數,這會強制當前線程放棄剩下的時間片,並休息 1 毫秒(由於不是實時操做系統,時間沒法保證精確,通常可能會滯後幾毫秒或一個時間片)。但所以的好處是,全部其它就緒狀態的線程都有機會競爭時間片,而不用在意優先級。

  優點:能夠調度任何處理器的線程使用時間片。不管有沒有符合的線程,都會放棄 CPU 時間,所以 CPU 佔用率較低。

  劣勢:相比 Thread.Sleep(0),由於至少會休息必定時間,因此速度要更慢。 

 

實驗告訴你:單一線程

  測試環境:Windows 7 32位、VMWare workstation、單處理器單核芯

  開發環境:Visual Studio 20十二、控制檯項目、Release 編譯後將 exe 拷貝到虛擬機後運行

 

  Thread.Yeild

static void Main()    
{
        string s = ""; 
        while (true)
        {      
              s = DateTime.Now.ToString(); //模擬執行某個操做
              Thread.Yeild(0);
        }    
}        

 

  執行效果 

 

  Thread.Sleep(0)

static void Main()    
{
        string s = "";
        while (true)
        {
            s = DateTime.Now.ToString(); 
            Thread.Sleep(0);
        }
}

 

  執行效果 

 

  Thread.Sleep(1)

static void Main()    
{
        string s = "";
        while (true)
        {
             s = DateTime.Now.ToString();
             Thread.Sleep(1);
        }
}

 

  執行效果 

 

  經過上述三個實驗,很明顯說明 Thread.Sleep(1) 對於解決資源 100% 佔用是有明顯效果的。

 

實驗告訴你:多線程(同優先級)

  個人實驗方法很簡單,就是經過 while 讓該線程不斷的執行,爲了讓你們一目瞭然兩個線程的交替,經過向控制檯輸出不一樣的字符串來驗證。

  while 語句執行速度至關快,因此必須因此加上一些代碼來浪費CPU時間,以避免你們只能看到不斷的刷屏。

 

  輔助代碼

private static void WasteTime()    
{
        // 耗時約 200ms
        DateTime dt = DateTime.Now;
        string s = "";
        while (DateTime.Now.Subtract(dt).Milliseconds <= 200)
        {
            s = DateTime.Now.ToString(); //加上這句,防止被編譯器優化
        }
}

 

  不使用任何方法 

  讓線程們本身爭用 CPU 時間。

    
static void Main(string[] args)    
{
        Thread t = new Thread(() =>
        {
            while (true)
            {
                 WasteTime();
                 Console.WriteLine("Thread 1 ==========");
            }
        });
        t.IsBackground = true;
        Thread t2 = new Thread(() =>
        {
            while (true)
            {
                WasteTime();
                Console.WriteLine("Thread 2");
            }
        });

        t2.IsBackground = true;
        t2.Start();
        t.Start();
        Console.ReadKey();

        t.Abort();
        t2.Abort();
        Console.ReadKey();
}

 


  執行效果 

 

  Thread.Yeild

  修改第一個線程的方法體,加入Thread.Yield。其他代碼不變。

Thread t = new Thread(() =>
{
        while (true)
        {
            WasteTime();
            Thread.Yield();
            Console.WriteLine("Thread 1 ==========");
        }
});

 

  執行效果 

 

  Thread.Sleep(0)

  仿照 Thread.Yield,只不過用 Thread.Sleep(0)替換。

 

  執行效果

 

  Thread.Sleep(1)

  仿照 Thread.Yield,用 Thread.Sleep(1)替換。

 

  執行效果

 

 

  從上面的示例看出,未使用 Thread 的這幾個方法(Yield,Sleep)的例子,兩個同等優先級的線程能夠得到差很少徹底同樣的CPU時間。

  而使用了 Thread 方法的那幾個例子或多或少都讓另外一個線程多獲取了些時間片,可是不一樣的方法執行效果差得並很少。這是由於它們所讓出的時間片每每都只是幾十毫秒的事情,這麼短的時間,對於個人測試代碼來講很難順利撲捉每一個瞬間。

Thread2 要獲得更多的時間片

 

 

實驗告訴你:多線程(不一樣優先級)

  先來調整下代碼,修改 Thread 1,讓它的優先級變成 「AboveNormal」。

    
Thread t = new Thread(() =>
{
        while (true)
        {
            WasteTime();
             Console.WriteLine("Thread 1 ==========");
        }
});

t.Priority = ThreadPriority.AboveNormal; // 加入這句話
t.IsBackground = true;

 

  不使用任何方法 

Thread t = new Thread(() =>
{
        while (true)
        {
            WasteTime();
            Console.WriteLine("Thread 1 ==========");
        }    
});    

t.Priority = ThreadPriority.AboveNormal;   
t.IsBackground = true;      

Thread t2 = new Thread(() =>    
{
        while (true)
        {
            WasteTime();
            Console.WriteLine("Thread 2");
        }    

});   

t2.IsBackground = true;

 

  執行效果 

  從實驗中能夠代表,低優先級的幾乎不多有使用時間片的時候。

 

  Thread.Yeild

  修改 Thread 的方法體

Thread t = new Thread(() =>    
{
        while (true)
        {
            WasteTime();
            Console.WriteLine("Thread 1 ========== {0}",Thread.Yield());
        }    
});

 

  執行效果 

 

  使用了 Yeild 以後,很明顯低優先級的線程如今也可以得到CPU時間了。

 

  Thread.Sleep(0)

  修改方法體

Thread t = new Thread(() =>    
{
        while (true)
        {
            WasteTime();
            Console.WriteLine("Thread 1 ==========");
            Thread.Sleep(0);
        }    
});

 

  執行效果 

 

  有沒有發現低優先級的線程又一次被無情的拋棄了?

 

  Thread.Sleep(1)

  仿照 Sleep(0),用 Sleep(1)替換。

 

  執行效果

 

  經過上面的實驗,我想你應該已經比較清楚了在不一樣優先級的狀況下,哪一個方法更適用於去切換線程。

 

本人觀點

  有鑑於 Thread.Sleep(0) 的表現,本人認爲應該無情的把它取締掉。至因而用 Thread.Yeild,仍是 Thread.Sleep(n) (n>0),那就根據實際狀況吧。歡迎你們補充~

 

參考資源

     Consequences of the scheduling algorithm: Sleeping doesn't always help

     SwitchToThread/Thread.Yield vs. Thread.Sleep(0) vs. Thead.Sleep(1)

 

 

  本文來源《C# 基礎回顧: Thread.Sleep(0) vs Sleep(1) vs Yeild

相關文章
相關標籤/搜索