計算密集型任務要進行大量的計算,消耗CPU資源,如視頻解碼等,啓用與CPU核心數相同的並行任務數可最大化利用CPU資源和加快任務的執行;IO密集型任務,如網絡、磁盤IO等,CPU消耗很少,任務的大部分時間都在等待IO操作完成(因爲IO的速度遠遠低於CPU和內存的速度),任務數適當增多,CPU效率將提高。
實現多任務有如下幾種模式:
- 多進程模式:在多核CPU上運行多個進程(數量與CPU核心數相同)可充分利用多核CPU計算能力。由於系統同時運行的進程數少,因此係統調度也非常高效。但每個系統允許同時運行的最大進程數量是有限的;
- 多線程模式;
- 多進程 + 多線程模式;
- 多協程模式;
- 異步IO複用模式:也稱事件驅動。Nginx就是支持異步IO複用的Web服務器,在單核CPU上採用單進程就可以高效地支持多任務。用異步IO複用來實現多任務也是一個主要的應用趨勢,但是無法充分利用多核CPU的計算能力。
下文將通過對線程、LWT、協程等相關概念的介紹來反映這些構建模式的差異。
線程
線程通常由線程ID、當前指令指針(PC)、寄存器集合和堆棧組成,是進程中的一個實體,是系統獨立調度分派的基本單位;線程也有就緒、阻塞和運行三種基本狀態。進程是系統資源分配的基本單位,創建、撤消、切換開銷大,進程間通信IPC(Inter-Process Communication)複雜,在SMP、MPP、NUMA系統上同時運行多個線程並行執行更爲合適。線程的實體包括程序、數據和線程控制塊(Thread Control Block,TCB),TCB包括以下信息:
- 線程狀態;
- 當線程不運行時,被保存的現場資源;
- 一組執行堆棧;
- 存放每個線程的局部變量主存;
- 訪問同一個進程中的主存和其它資源。
SMP(Symmetric Multi-Processor)系統中有多個處理器(核心),每個處理器都有自己的控制單元、算術邏輯單元和寄存器,都可以通過某種互聯機制(通常是系統總線)訪問一個共享的主存和I/O設備,還可以通過存儲器中共享地址空間中的消息和狀態信息相互通信。SMP廣泛應用於PC和移動設備領域,並行度很高,能夠顯著提升性能。在SMP系統中,只運行操作系統的一個拷貝,不同處理器被授權均勻訪問(Uniform Memory Access,UMA亦稱作統一尋址技術或統一內存存取)存儲器的不同部分,但同一時間只能有一個處理器訪問存儲器。
單核CPU上運行多線程的鎖問題:線程在被阻塞或睡眠狀態下,其已經持有的資源訪問權不能釋放才能保證併發訪問資源的最終一致性。因此,多個併發線程訪問多個不同對象時,有可能導致各自同時獲得一部分資源訪問權,而等待對方釋放另一部分資源訪問權被阻死,於是死鎖發生。
單核CPU上運行多線程的效率問題:在CPU密集型作業條件下,多線程的確不能提高性能,甚至更浪費時間;但是,在IO密集型作業條件下,多線程則可以提升性能。
線程有以下三種常見的模型:
- 內核級線程(Kernel-Level Thread):內核級線程駐留在內核空間,由操作系統調度器管理、調度和分派;內核級線程在其生命期內都將被映射到一個內核線程,一旦終止,兩個線程都將離開系統;內核級線程不受用戶態上下文的拖累,資源同步和數據共享比進程要低一些。如下圖所示:
- 用戶級線程(User-Level Thread):用戶級線程的創建、調度、同步和銷燬是通過庫函數在用戶空間完成的,對內核都是不可見的,因此無法被調度到處理器內核。在任意給定時刻進程都只能運行一個用戶級線程,在整個程序執行過程中是進程而非其用戶級線程與一個內核線程關聯起來。用戶級線程的創建、銷燬、切換代價比內核級線程小得多,允許每個進程定製自己的調度算法,線程管理比較靈活,還能在不支持線程的操作系統中實現。如下圖所示:
- 混合模型:在支持多線程的操作系統中,不同系統實現的線程模型並不相同,有的實現了用戶級線程模型,有的實現了內核級線程模型,還有的二者兼具如下圖:
輕量進程(Lightweight Process,LWP)
a LWP runs in user space on top of a single kernel thread and shares its address space and system resources with other LWPs within the same process.
LinuxThreads 所採用的是線程-進程1:1模型,即一個用戶級線程對應一個輕量進程。LinuxThreads 的線程調度由內核完成,線程創建、同步、銷燬由核外線程庫(線程包)來完成。
NPTL(Native Posix Thread Library) 作爲 LinuxThreads 之後的替代者仍然採用1:1的線程模型,NPTL沒有使用管理線程,LWP的管理直接放在覈內進行。NPTL仍然不是100% POSIX兼容的,但就性能而言相對 LinuxThreads 已經有很大程度上的改進了。
協程(Coroutine)
協程又稱微線程,一個線程可以包含一個或多個協程,協程的相關特性如下:
- 運行在用戶態,是系統內核不可感知的,有自己的目態棧,相當於用戶級線程;
- 異步編碼結構同步化;
- 從內部掛起主動釋放CPU而非線程的搶佔式調度切換,共享資源不加鎖;
- 多個協程在同一個線程上下文中執行,同一時間只有一個是處於激活態,其他都是暫停態(suspended),無法充分利用多核CPU的計算能力;
- 目態棧數據量小,切換更快。
其實,協程先於搶佔式調度切換的線程出現用來模擬多任務併發,但由於其主動釋放CPU而非搶佔式(Non-Preempt)調度切換導致多任務時間片不均衡,而後線程出現了;協程近年來又開始出現流行的趨勢。win32中稱協程爲纖程(Fiber),主要用於舊的Unix應用的移植,創建新應用還是建議優先使用線程。
最後的總結語
- 在支持多線程的系統中,進程是系統資源分配的基本單位,線程是系統獨立調度的基本單位;
- 無論系統支持什麼樣的線程模型,操作系統調度器指派到處理器內核進行處理的對象是內核線程;
- 用戶級線程在用戶空間調度靈活高效,內核級線程在內核空間調度成本更高;
- 用戶級線程在本進程內競爭CPU處理資源,內核級線程在全系統內競爭CPU處理資源,因此後者才能發揮SMP、MPP、NUMA並行處理的優勢;
- 進程的創建、撤消、切換成本都高於線程,故使用多個線程比使用多個進程更有管理維持上的性能優勢;
- 由於多線程共享進程資源會導致任何一個線程掛掉都可能造成整個進程崩潰,多進程應用程序在出現進程池內的進程崩潰或被攻擊的情況下表現更加健壯,一個子進程崩潰不會影響主進程和其他子進程。因此Chrome瀏覽器爲每一個標籤頁運行一個進程,Apache最早也是採用多進程模式。在Windows下,多線程的效率比多進程要高,所以微軟的IIS服務器默認採用多線程模式。由於多線程存在穩定性的問題,IIS的穩定性就不如Apache。爲了緩解這個問題,IIS和Apache現在又有多進程+多線程的混合模式;
- 協程切換快,不存在鎖安全問題,異步編碼結構同步化,要充分利用多核CPU的計算能力,還要以多進程或多線程爲載體。
- 異步I/O複用不切換線程或進程,效率很高。