1. 程式人生 > >執行緒退出歷史舞臺

執行緒退出歷史舞臺

執行緒

執行緒是指程序中的一個單一順序的控制流,是作業系統能夠排程的最小單位,一個程序中可以有多條執行緒,分別執行不同的任務。執行緒有核心執行緒和使用者執行緒之分,但在本文中僅指核心執行緒。在軟體開發中,使用執行緒有以下好處:
1、在多核或多路 CPU 的機器上多執行緒程式能夠併發執行,提高運算速度;
2、把 I/O,人機互動等與密集運算部分分離,提升 I/O 吞吐量和增進使用者體驗。
執行緒的缺點也很明顯:
1、建立一條執行緒需要較大的記憶體開銷,導致不能建立海量的執行緒;
2、執行緒由作業系統排程(分配時間片),執行緒切換的 CPU 成本比較高,導致大量執行緒存在時大量 CPU 資源消耗線上程切換上;
3、同一程序的多條執行緒共享全部系統資源,在多執行緒間共享資源需要進入加鎖,大量的鎖開銷不提,重要的是加大了編寫程式的複雜性,這一點你看看有多少書名含有“多執行緒”三個字就明白寫個多執行緒應用有多難了;
4、I/O 方面,多執行緒幫助有限,以 TCP Socket Server 為例,如果每一個 client connection 由一條專屬的執行緒服務,那麼這個 server 可能併發量很難超過 1000。為了進一步解決併發帶來的問題,現代伺服器都使用 event-driven i/o 了。
event-driven i/o 解決了併發量的問題,但引入了“程式碼被回撥函式分割得零零碎碎”的問題。特別是當 event-driven i/o 跟 multi-threading 結合在一起的時候,麻煩就倍增了。解決這個問題的辦法就使用綠色執行緒,綠色執行緒可以在同一個程序中成千上萬地存在,從而可以在非同步 I/O 上封裝出同步的 APIs,典型的就是用基於 greenlet + libevent 開發的 python 庫 gevent。綠色執行緒的缺陷在於作業系統不知道它的存在,需要使用者進行排程,也就無法利用到多核或多路 CPU 了。為了解決這個問題,很多大牛都做出了巨大的努力,並且成果斐然,scala、google go 和 rust 都較好地解決了問題,下文以 rust 的併發模型為例講一下。
rust 提出一個 Task 的概念,Task 有一個入口函式,也有自己的棧,並擁有程序堆記憶體的一部分,為方便理解,你可以把它看作一條綠色執行緒。rust 程序可以建立成千上萬個 Tasks,它們由內建的排程器進行排程,因為 Tasks 之間並不共享資料,只通過 channels/ports 通訊,所以它們是可並行程度很高。rust 程式啟動時會生成若干條(數量由 CPU 核數決定或執行時指定)執行緒,這些執行緒並行執行 Tasks,從而利用多個 CPU 核心。


如上圖,rust 應用程式不停地 spawn 出一個又一個 Tasks,它們由 tasks 排程器管理,在適當的時機,排程器會把某一個 Task 分配給原生執行緒執行,如果這個 Task 進入 I/O 等待或主動讓出 CPU(sleep),那麼這個 Task 會被交回給排程器,而相應的原生執行緒會執行另一個新分派的 Task。儘管使用 rust 程式語言是不能建立執行緒的(直接呼叫 C 函式不算),但 rust 應用程式實際上是多執行緒的(一般情況下),它能夠充分地利用多核或多路 CPU。
綜上,類似 rust 的 Task 的概念是比執行緒更好的併發模型,更安全,編寫的程式碼也更加容易維護(關於維護性,我相信寫過 gevent 程度或 go 程式的同學會認同的)。執行緒當然不會消亡,但隨著 scala/go/rust 的成熟,在可以預見的將來,執行緒會退到它呆著的角落:遠離普通程式設計師,只有少數人需要了解它的細節。