賴勇浩(http://laiyonghao.com)python
線程是指進程中的一個單一順序的控制流,是操做系統可以調度的最小單位,一個進程中能夠有多條線程,分別執行不一樣的任務。線程有內核線程和用戶線程之分,但在本文中僅指內核線程。在軟件開發中,使用線程有如下好處:
一、在多核或多路 CPU 的機器上多線程程序可以併發執行,提升運算速度;
二、把 I/O,人機交互等與密集運算部分分離,提高 I/O 吞吐量和增進用戶體驗。
線程的缺點也很明顯:
一、建立一條線程須要較大的內存開銷,致使不能建立海量的線程;
二、線程由操做系統調度(分配時間片),線程切換的 CPU 成本比較高,致使大量線程存在時大量 CPU 資源消耗在線程切換上;
三、同一進程的多條線程共享所有系統資源,在多線程間共享資源須要進入加鎖,大量的鎖開銷不提,重要的是加大了編寫程序的複雜性,這一點你看看有多少書名含有「多線程」三個字就明白寫個多線程應用有多難了;
四、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 的成熟,在能夠預見的未來,線程會退到它呆着的角落:遠離普通程序員,只有少數人須要瞭解它的細節。程序員