自覺得是的多線程(一)

    多線程在web開發裏面其實應用場景並很少,並且應用到多線程的場景也大多都是一些比較簡單的場景,基本上大多均可以用Task代替,因此不少web開發人員對多線程的理解很是的淺薄,也就致使了會出現不少不可預計的bug,而後又所以寫了一大堆邏輯來繞來繞去,因此我想談談多線程,試圖作到高屋建瓴,給你們一個比較開闊的視野。web

    這一篇先從性能角度來講,下一篇從線程安全角度來說解。算法

    先解釋一下,爲何線程不是越多越好:數據庫

    談到多線程必需要談到cpu,一個單核心cpu在同一個時間內,只能執行一個線程(受限於目前技術),對於超線程cpu(intel專利,使用了超線程技術的cpu,允許一個核同時執行兩個線程,在windows操做系統裏面也會顯示有兩個核心),咱們都理解成是雙核心cpu。windows

    而對於每一個線程,都必定包含如下幾個要素:緩存

    一、線程內核對象,系統爲每一個線程建立的,包括上下文(context)等。安全

    二、線程環境塊,是一塊內存,包含線程的異常處理鏈首。多線程

    三、用戶模式棧、內核模式棧。性能

  四、DLL線程連接和線程分離通知spa

    這個是咱們在建立一個新線程時,系統在爲線程初始化時須要建立的東西。操作系統

    而前面又提到了,一個單核CPU同一時間內只能跑一個線程,那麼當一個CPU正在跑一個線程的時候,這個線程的不少數據已經存到了CPU的高速緩存中,這個時候發生了切換,咱們須要切換到另一個線程上面去執行,那這個線程執行的多是另外的代碼,須要讀取另外的數據,這個時候CPU就須要從新去內存裏面去取數據,來填充這個高速緩存。而CPU去內存裏面取數據的這個過程,相比於去高速緩存裏面取數據來講,是很慢的,這也就是說,當咱們頻繁切換線程的話,CPU就須要作不少額外的事情。這也就是說,當只有單核CPU時,一樣的功能,一個線程確定比多個線程更快。這裏有一個矛盾,就是系統不可能只有一個線程,系統還牽涉到不少本身的系統線程,以及其它的應用程序的線程,那系統在作線程切換(系統決定調用哪一個線程)的時候,會牽涉到一個線程優先級的問題,而這個調度方法(相對合理智能的一個算法)是咱們不可控的,因此說當你的程序有多個線程的狀況下,在作線程切換的時候,切換到你的線程上面的機率會變大,因此也不能絕對的說一個線程就必定比多個線程快,這裏只是一個大概的相對理論狀況。

    既然是這種狀況,那咱們在寫代碼的時候,就應該儘量的避免出現線程切換,而讓CPU儘量的執行該線程。但是在什麼狀況下會容易出現線程切換(如下全部的線程切換都是指的相對或是可能,由於線程調度不可控)。

    好比我有如下代碼:

public static string ReadText(string path)
        {
            string text = "";
            if (File.Exists(path))
            {
                using (Stream fs = File.Open(path, FileMode.Open))
                {
                    using (StreamReader sr = new StreamReader(fs))
                    {
                        text = sr.ReadToEnd();
                        sr.Close();
                    }
                }
            }
            return text;
        }

    這個是很常見的IO讀取,這個時候會給IO線程發出一個請求,而後等待IO的響應,在等待的過程當中,系統會把這個線程鎖定(這是個很棒的設計),讓CPU去作其它事情,等到執行響應完畢之後,再喚醒該線程。同理,在作數據庫的讀取以及一些其餘的IO請求時,都會這樣。假如當有一個用戶請求時,咱們就會執行這樣一段代碼,那當有不斷多的用戶請求時,系統就會建立不斷多的線程(建立線程的自己開銷就很昂貴),而當IO讀取完響應的時候,又會有不斷多的線程逐漸被喚醒,系統這個時候就又會疲於線程切換,你就會發現性能開始巨降。

同理,當我有如下代碼時:

lock(object){
    ...
}

當多個線程在執行這段代碼的時候,就會出現頗有意思的狀況。

假如1-10,一共有10個線程須要執行到這段代碼,假如cpu是2個(分別爲A、B,分別執行的是一、2),當1執行的時候,2被鎖定,這個時候B CPU就開始作線程切換,調度3-10中的任意一個繼續執行,若是這段代碼較長,A不必定能在短期內執行完,那就會出現,調度一個鎖一個,繼續切換,再調度,再鎖定,再切換的一個循環,一直到全部線程都被鎖定爲止。

當1執行完畢的時候,這個時候喚醒全部被鎖定的線程,而後從新分配給A、B兩個CPU,而後當A又執行到這裏的時候,B又會出現剛剛再調度、再鎖定,再切換這樣的一個循環狀況。因此在多線程開發的時候,儘量的避免共享資源的出現。

 

    ps:那當咱們在作多線程開發的時候,何時用線程池、何時本身寫線程。Task也能夠理解爲線程池。

    線程池的優點就是,當邏輯執行完畢之後,並不銷燬線程,而是將線程掛起,當你須要使用線程的時候,就會給你分配一個空閒的線程去執行你的邏輯,讓線程反覆使用,由於前面有提到了初始化線程須要建立不少對象,開銷很昂貴。只有當全部的線程都處於繁忙狀態時,沒有線程分配時纔去給你從新建立一個新線程。

    但是若是你的程序在某一個時間段有一個峯值的話,那麼最繁忙的時候,程序就會建立N多個線程,而當峯值過去了之後,這些被建立的線程不會被釋放掉,會一直佔用這你的資源。一直等到GC,纔有可能被釋放掉。

    因此各位看客根據本身的業務狀況來決定,是否使用線程池。

相關文章
相關標籤/搜索