一、線程開銷 操作系統創建線程是有代價的,其主要開銷在下麵列舉出來了。 記憶體開銷 1. 線程內核對象 擁有線程描述屬性與線程上下文,線程上下文占用的記憶體空間為 x86 架構 占用 700 位元組、x64 架構 1240 位元組 、ARM 架構 350 位元組。 2. 線程環境塊(TEB) TEB 消耗一個 ...
一、線程開銷
操作系統創建線程是有代價的,其主要開銷在下麵列舉出來了。
記憶體開銷
線程內核對象
擁有線程描述屬性與線程上下文,線程上下文占用的記憶體空間為 x86 架構 占用 700 位元組、x64 架構 1240 位元組 、ARM 架構 350 位元組。
線程環境塊(TEB)
TEB 消耗一個記憶體頁,占用 4KB記憶體。
用戶模式棧。
用戶模式棧存儲傳遞給方法的局部變數與實參,並且還存儲有一個地址用於當前方法返回的時候,線程應該從哪個地方繼續執行。預設 Windows 分配保留 1MB 記憶體。
內核模式棧。
32 位 Windows 占用 12 KB,64 位 Windows 占用 24 KB。
DLL 線程連接與線程分離通知。
這種策略只有 Windows 才會存在,當創建線程時, Windows 會調用進程所有非托管 DLL 的 DllMain 方法,並未其傳遞 DLL_THREAD_ATTACH 標誌,線程終止時傳遞 DLL_THREAD_DETACH 標誌。
線程上下文切換與 CPU 之間的關係
Windows 在任何時刻都只會將 1 個線程分配給 1 個 CPU ,該線程享有一個時間片的運行時間。時間片到期之後,Windows 會將上下文切換到另外一個線程,動作如下:
- 將 CPU 寄存器值存儲在當前正在運行的線程的內核對象內部的上下文結構之中。
- 從先有線程集合選取一個線程供調度,如果該線程屬於另一個進程,還得切換 CPU 能夠操作的虛擬地址空間。
- 將上下文結構中的值載入到 CPU 寄存器之中。
以上操作做完之後,Windows 等待這個線程時間片到期,執行下次切換,每次切換的時間開銷大概為 30 毫秒。
如果一個線程時間片結束之後,下一個調度的線程還是之前的線程則不會產生線程上下文切換。
所以在理想狀態下,每個系統最佳的線程數應該與其核心數相同,(如果是 4 核 8 線程則最優應該為 8 個)因為這樣上下文切換出現的情況就會少很多。
最重要的是,Windows 系統上大部分程式線程都處於空閑狀態,但是線程占用的記憶體空間是事實存在的。
三、使用專用線程執行計算限制的非同步操作
一般來說不推薦使用 Thread 手動創建線程,而應該使用線程池,不過在有以下需求時,可以手動創建線程。
- 需要設定更高的線程優先順序的時候。
- 需要將線程設置為前臺線程。
- 某些長耗時的專用線程。
- 該線程可能會通過 Thread 的 Abort 方法終止自身。
在調用過程中,如果使用了 Thread.Join()
方法那麼就會造成調用線程阻塞當前代碼,直到創建的線程被終止。
四、為什麼要使用線程
- 針對於客戶端程式而言,多線程可以增強響應性,不會因為耗時操作阻塞 UI 線程造成用戶體驗卡頓。
- 針對於伺服器程式而言,可以併發地處理用戶請求,充分利用多核 CPU 的優勢。
作者的觀點是,電腦的 CPU 使用率應該保持 100% 的使用率才不算是浪費計算資源。
五、線程調度與優先順序
搶占式系統通過優先順序來判定線程在什麼時候調度多少時間,每個線程都分配了從 0 到 31 的優先順序,系統為 CPU 分配線程時,首先檢查 31 的線程,並以輪詢的方式調度他們(優先順序都為 31)。
如果高優先順序的線程一直處於調度狀態,那麼操作系統不會將 CPU 分配給低優先順序的線程,這樣就會造成 線程饑餓。
較高的優先順序線程總會搶占低優先順序線程,即便該線程的時間片沒有用完。
CPU 會創建一個優先順序為 0 的 零頁線程 ,該線程是系統唯一一個優先順序為 0 的線程,只有在 CPU 空閑的時候會執行他,用於清理 RAM 中所有的空閑記憶體頁。
【註意】
進程優先順序類 + 線程優先順序構成了一個基礎優先順序,Windows 還有一個動態優先順序用於防止產生線程饑餓,會動態調成線程的優先順序狀態。
但是動態優先順序只會針對基礎優先順序在 0 ~ 15 的線程應用,16 ~ 31 不受這個管控。
Windows 通過兩個抽象層用於表示進程優先順序類和線程優先順序,單一般 C# 用戶代碼中能夠控制的只有線程優先順序,他們分別是:Lowest、BelowNormal、Normal、AboveNormal、Highest。
六、前臺線程與後臺線程
在 CLR 中線程只有兩種狀態,前臺線程和後臺線程,而且當所有前臺線程被終止之後,CLR 會強行關閉所有後臺線程,並退出程式。
線程在運行的生命周期當中可以變更其狀態,但主線程預設為前臺線程,使用 Thread 類型創建的線程預設也是前臺線程。只有線程池的線程預設為後臺線程,進入托管執行的本機代碼創建的任何線程也會標記為後臺線程。