前言
有一陣子都在用 Photon Server 開發遊戲後端, 語言也是 C#, 使用起來並不陌生. 然而陌生的是多執行緒的概念, Photon 提供一個 PoolFiber 的類別, 功能類似制定 Schedule, 能將一個個的指令都往裏頭丟並依序執行.
一個完整的 Server 能同時啟用多個 Fiber , 以此做到非同步運行 Server.
主旨
近期整理了相關的概念, 像是 Process (處理程序)、Thread (執行緒) 、Context Switch (工作切換) 以釐清非同步的運作原理.
本篇將分上、下集討論以下內容:
上集: 基本概念 Process, Thread, Context Switch
下集: C# .NET 如何操作 Thread
Process、Thread、Context Switch
早期電腦剛出現時, 作業系統本身與應用程序都是以一行行的指令接續執行. 如果指令包含 Function (函式), 就要等函式回傳結果後在繼續下一個指令. 運作起來就像是排隊一樣.

這種運作模式下會有幾個問題. 首先是每個指令都須等到前一個指令 (作業系統與應用程序交雜) 完成才能接續, 若前個指令進入的無窮迴圈導致後續無法執行, 就只能重開機了.
再者是使用者體驗, 當開啟了需要運行一段時間的應用程式, 整個運行期間都必須等著, 而不能做其他事情, 看起來就像當掉了一樣.
為了解決上述問題 Process (處理程序) 的概念便出現了, 使每個應用程序都能獨立運行. Process 是實際運行應用程序的實體 (Instance), 它佔據一塊記憶體空間,
包含了程式碼與相關資源, 而這個空間只有該應用程序能存取, 排除了其他程序的使用權. Process的出現, 使各應用程序間不互相影響, 也隔離了程序與作業系統, 讓作業系統更穩定.

然而 Process 並非萬能, 雖然解決的應用程序間相依性的問題, 但硬體上仍有相依性. 所有 Process 都共用著 CPU(s), 若占用該 CPU 的 Process 進入了無窮迴圈, 該CPU 也無法被其他 Process 使用.
為了解決共用 CPU 的問題, Thread (執行緒) 的概念便出現了, 使 CPU 有著分身的能力, 能同時處理多個 Process.
Jeffrey Richter 在《CLR via C# 第四版》中說:「執行緒是 Windows 作業系統用來虛擬化 CPU 的概念。」
意思是, Windows 作業系統為每個應用程序配置一條專屬的執行緒 (就像是 CPU), 所以當應用程序陷入無窮迴圈, 其他應用程序照常能執行. 以 Thread 為一個時間基本單位, 切割 CPU 的運行時間.
舉個例子:
Thread A 運行 30ms, 換 Thread B 運行, 以此類推. 如此一來, CPU 就好像能「同時」執行多個工作.

Thread 同時也會帶來負擔, 包括記憶體空間與執行效能, 其主要來自於 Context Switch (工作內容切換).
以生活上舉例, 當需要同時處理多件工作時, 進行中的工作必須把目前進度、待辦事項等資料記錄下來, 然後再把另一件工作的紀錄取出來, 查看進度與後續動作, 接著繼續處理工作. 對 CPU 而言也是如此, 當載入多個應用程序時, 作業系統就必緒適當分配合理的運算時間給這些應用程序的執行緒. 像這樣將 CPU 的資源從分配給某個執行緒切換到另一個執行緒的行為就是 Context Switch.

Context Switch 可能大大影響 CPU 效能. 因 CPU 本身內建多個 Cache (快取), 為的是提升 CPU 效率, 不需每次都重新將應用程序的資料載入記憶體. 但由於頻繁切換執行緒, 使得剛載入的快取資料很快就被下一個執行緒的資料覆蓋, 無法有效發 Cache 的功能. 因此建議, 設計程式時能盡量避免執行緒切換的動作, 以提高程式效能.
近代 CPU 硬體有很大福的提升, 多核 CPU、超頻 CPU 的架構很普遍. 上述提到的多執行緒的隱憂也得到很大的改善, 多核心 CPU 能真正分配多個執行緒給每顆核心運行. 不過執行緒數量大於 CPU 數量時還是會有 Context Switch 的情況發生.
觀察檢視

我能們能簡單從工作管理員「效能」頁籤看到當前執行緒數量. 以及切換至「詳細資料」, 打開「執行緒」欄位, 看到每個應用程序的執行緒數量.

ServiceHub.Host.CLR 占了多數 (645個執行緒), 再來是System. ServiceHub.Host.CLR 是 Visual Studio 2017/2019 的後台應用程式, 它會連接到 MS 服務器, 進行一些不可描述的事情(!?)
以我這台電腦安裝的 Visual Studio 2019 來看, 它的執行緒數量是 Visual Studio 主程式的 10 幾倍(645/55=11.72…). 姑且不討論開發者隱私的問題, 執行緒的數量是否過多仍是個問題影響著開發效率.
總結
執行緒帶來的好處是顯而易見的, 儘管有些負擔, 仍是用的甘之如飴.
本篇上集到此結束, 下一集將接著實際操作看看C# .NET Thread 類別, 體驗簡易的建立以及如何使用執行緒.
謝謝觀看 😀
參考
C# 學習筆記 – https://www.huanlintalk.com/2013/04/csharp-notes-multithreading-1.html
在〈C# 多執行緒 – (1)概念篇〉中有 1 則留言