2012 不宜進入的三個技術點(中)

賴勇浩(http://laiyonghao.com

線程

線程是指進程中的一個單一順序的控制流,是操作系統能夠調度的最小單位,一個進程中可以有多條線程,分別執行不同的任務。線程有內核線程和用戶線程之分,但在本文中僅指內核線程。在軟件開發中,使用線程有以下好處:
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 的成熟,在可以預見的將來,線程會退到它呆着的角落:遠離普通程序員,只有少數人需要了解它的細節。

發佈了211 篇原創文章 · 獲贊 640 · 訪問量 364萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章