《CLR Via C#》讀書筆記:26.執行緒基礎
一、執行緒開銷
作業系統建立執行緒是有代價的,其主要開銷在下面列舉出來了。
記憶體開銷
執行緒核心物件
擁有執行緒描述屬性與執行緒上下文,執行緒上下文佔用的記憶體空間為 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 型別建立的執行緒預設也是前臺執行緒。只有執行緒池的執行緒預設為後臺執行緒,進入托管執行的本機程式碼建立的任何執行緒也會標記為後臺執行緒。