操作系統筆記-3-線程

多線程出現

傳統的進程都是單線程的程序。我們總是希望自己的程序有更高的並行性,傳統進程也是有辦法實現這種並行性,那就是通過子進程,但是子進程是獨立的數據空間,很多時候程序的不同任務都是需要訪問相同數據的,因此子進程有很大的侷限性。

在這種需求場景下,多線程出現了,它彌補了子進程的缺陷,因爲進程內的線程共享進程的資源,可以很容易實現數據共享。

線程概念

線程在進程內部,它共享進程的地址空間。

線程屬性

線程共享執行代碼,data分段和打開的文件。但是有獨立寄存器、程序計數器、狀態和棧。第一列是線程共享的進程屬性,第二列是線程獨立的屬性。
在這裏插入圖片描述

線程和進程關係

下圖是一個進程包含3個線程
在這裏插入圖片描述

線程狀態

同傳統進程一致

多線程實現

用戶態多線程
  • 用戶態多線程結構
    在這裏插入圖片描述
    1. Run-Time System:用戶態實現的多線程,線程的創建、調度、銷燬都是在用戶態,因此運行時系統負責管理線程調度,每個進程都有一個運行時系統
    2. Thread table:線程表,記錄當前進程擁有的線程
  • 用戶態多線程的優點
    1. 線程切換不需要切換上下文
    2. 線程切換不需要刷新緩存
    3. 可以在不支持線程的系統中實現多線程
  • 用戶態多線程的缺點
    1. 雖然實現了多線程,但是因爲對系統是透明的,系統並不知道進程是多線程,因此用戶態多線程並不能真正並行,只能利用單核
    2. 阻塞系統調用,當進程的一個線程調用了阻塞系統調用時,整個進程都會陷入阻塞狀態。內核線程阻塞了之後,無法通知進程運行時系統線程調度器導致的進程阻塞。解決方案是jacket,將阻塞系統調用替換爲非阻塞系統調用,比如同步的I/O替換爲異步I/O
    3. 缺頁中斷也會導致進程阻塞
    4. 用戶態線程因爲沒有時鐘中斷,因此不能實現輪轉調度
內核態多線程
  • 內核態多線程結構
    在這裏插入圖片描述
    1. 內核線程由系統調度
    2. 線程表,在內核空間
  • 內核態多線程優點
    1. 可以利用多核cpu,能真正提升進程的並行
    2. 阻塞系統調用問題不會導致進程阻塞,因爲內核多線程是由系統調用,系統管理着線程,因此知道線程已阻塞,就可以調度其他線程執行
    3. 缺頁中斷時可以切換其他線程執行
  • 內核態多線程缺點
    1. 內核線程每次切換都會陷入內核,從用戶態切換到內核態,這個過程是有開銷的
    2. 創建和銷燬線程開銷,線程複用
    3. 信號是發送給進程而不是線程
    4. 當線程創建新的進程時,新進程屬性(含有線程數)問題
混合實現
  • 混合實現結構
    混合模式下,一個內核進程可以映射多個用戶態線程
    在這裏插入圖片描述
  • 混合實現的優點
    實現了最大的靈活性
線程調度激活機制
  • 線程調度激活機制結構
    在這裏插入圖片描述
  • 避免阻塞
    爲了避免用戶態線程在調用阻塞系統調用時阻塞進程,內核在發現線程執行阻塞調用時會通過upcall通知進程,進程run-time system設置當前線程爲block狀態,然後調度其他線程運行;當阻塞操作調用完成會再次通知進程,run-time system可以選擇立即運行或者加入就緒隊列
  • 硬件中斷
    如果進程對該中斷不感興趣,可能是其他進程的I/O完成,run-time system可以忽略,然後恢復線程到中斷前的狀態繼續運行;如果進程對該中斷感興趣,比如可能是某個線程的缺頁加載完成,此時運行哪個線程取決於run-time system

多線程程序設計的挑戰

識別任務

識別出可以獨立、併發的任務,可以獨立運行在多核處理器上

平衡

考慮多核運行是否值得, 根據Amdahl定律,一個應用通過增加cpu可以獲得的加速,S代表任務必須串行執行佔比,N代表核心數
在這裏插入圖片描述

一個例子,橫座標是核心數,縱座標是加速倍數,曲線代表不同必須串行執行佔比在不同核心數下的加速
在這裏插入圖片描述

數據劃分

如果應用劃分爲獨立的任務,那麼任務訪問和操作的數據必須劃分到不同的cpu上

數據依賴

如果並行的任務訪問的數據之間有依賴,需要同步機制來保證數據一致性,在後續的筆記中會詳細介紹線程同步

測試和調試

當應用是由多線程實現時,測試和調試都比較困難

多線程應用舉例

Java的多線程模型

瞭解完多線程後,很好奇Java的多線程模型是怎麼實現的呢?JVM中並沒有說明Java的多線程是用戶態實現還是內核態實現。網上查了一番資料後,感覺知乎的R大的回答比較靠譜一點,鏈接。在較新的Hotspot VM,在除了Solaris的平臺上實現的是1:1的模型,也就是內核態實現多線程。感興趣的可以點擊上面的鏈接查看詳情。

Go語言的goroutine

Go語言中的goroutine就是一種用戶態多線程,它有自己實現的調度器。

總結

多線程在提升性能的同時也給編程帶來了複雜性。不過在開發過程中,不管是用什麼語言,都會經過一些封裝,使其可用性更高。比如Java的JUC下的線程池,它已經封裝了很多細節,讓我們只需要關注任務和數據的拆分,而不用關注多線程實現的細節。

參考
  1. Modern Operating Systems》第4版
  2. Operating System Concepts》第10版
  3. JVM中的線程模型是用戶級的麼?
  4. Golang 的 goroutine 是如何實現的?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章