自以為是的多線程(一)


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

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

    先解釋一下,為什么線程不是越多越好:

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

    而對於每個線程,都一定包含以下幾個要素:

    1、線程內核對象,系統為每個線程創建的,包括上下文(context)等。

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

    3、用戶模式棧、內核模式棧。

  4、DLL線程鏈接和線程分離通知

    這個是我們在創建一個新線程時,系統在為線程初始化時需要創建的東西。

    而前面又提到了,一個單核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,分別執行的是1、2),當1執行的時候,2被鎖定,這個時候B CPU就開始做線程切換,調度3-10中的任意一個繼續執行,如果這段代碼較長,A不一定能在短時間內執行完,那就會出現,調度一個鎖一個,繼續切換,再調度,再鎖定,再切換的一個循環,一直到所有線程都被鎖定為止。

當1執行完畢的時候,這個時候喚醒所有被鎖定的線程,然后重新分配給A、B兩個CPU,然后當A又執行到這里的時候,B又會出現剛剛再調度、再鎖定,再切換這樣的一個循環情況。所以在多線程開發的時候,盡可能的避免共享資源的出現。

 

    ps:那當我們在做多線程開發的時候,什么時候用線程池、什么時候自己寫線程。Task也可以理解為線程池。

    線程池的優勢就是,當邏輯執行完畢以后,並不銷毀線程,而是將線程掛起,當你需要使用線程的時候,就會給你分配一個空閑的線程去執行你的邏輯,讓線程反復使用,因為前面有提到了初始化線程需要創建很多對象,開銷很昂貴。只有當所有的線程都處於繁忙狀態時,沒有線程分配時才去給你重新創建一個新線程。

    可是如果你的程序在某一個時間段有一個峰值的話,那么最繁忙的時候,程序就會創建N多個線程,而當峰值過去了以后,這些被創建的線程不會被釋放掉,會一直占用這你的資源。一直等到GC,才有可能被釋放掉。

    所以各位看客根據自己的業務情況來決定,是否使用線程池。

 

    


免責聲明!

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



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