OS- -线程详解
OS- -線程詳解
文章目錄
- OS- -線程詳解
- 一、線程
- 1.線程的使用
- 多線程解決方案
- 單線程解決方案
- 狀態機解決方案
- 2.經典的線程模型
- 線程系統調用
- 3.POSIX線程
- 4.線程實現
- 在用戶空間中實現線程
- 在用戶空間實現線程的優勢
- 在用戶空間實現線程的劣勢
- 5.在內核中實現線程
- 6.混合實現
一、線程
- 在傳統的操作系統中,每個進程都有一個地址空間和一個控制線程。事實上,這是大部分進程的定義。
- 不過,在許多情況下,經常存在同一地址空間中運行多個控制線程的情形,這些線程就像是分離的進程。
下面我們就著重探討一下什么是線程
1.線程的使用
- 或許這個疑問也是你的疑問,為什么要在進程的基礎上再創建一個線程的概念,準確的說,這其實是進程模型和線程模型的討論,回答這個問題,可能需要分三步來回答:
- ?多線程之間會共享同一塊地址空間和所有可用數據的能力,這是進程所不具備的
- ?線程要比進程更輕量級,由于線程更輕,所以它比進程更容易創建,也更容易撤銷。在許多系統 中,創建一個線程要比創建一個進程快10-100倍。
- ?第三個原因可能是性能方面的探討,如果多個線程都是CPU密集型的,那么并不能獲得性能上的 增強,但是如果存在著大量的計算和大量的I/O處理,擁有多個線程能在這些活動中彼此重疊進 行,從而會加快應用程序的執行速度
多線程解決方案
-
現在考慮一個線程使用的例子:一個萬維網服務器,對頁面的請求發送給服務器,而所請求的頁面發送 回客戶端。
-
在多數web站點上,某些頁面較其他頁面相比有更多的訪問。
- 例如,索尼的主貞比任何一 個照相機詳情介紹頁面具有更多的訪問
- Web服務器可以把獲得大量訪問的頁面集合保存在內存中,避 免到磁盤去調入這些頁面,從而改善性能。
- 這種頁面的集合稱為高速緩存(cache),高速緩存也應用 在許多場合中,比如說CPU緩存。
- 上面是一個web服務器的組織方式,一個叫做 調度線程(dispatcher thread)的線程從網絡中讀 入工作請求,在調度線程檢查完請求后,它會選擇一個空閑的(阻塞的)工作線程來處理請求,通常是 將消息的指針寫入到每個線程關聯的特殊字中。
- 然后調度線程會喚醒正在睡眠中的工作線程,把工作線 程的狀態從阻塞態變為就緒態。
- 當工作線程啟動后,它會檢查請求是否在web頁面的高速緩存中存在,這個高速緩存是所有線程都可 以訪問的。
- 如果高速緩存不存在這個web頁面的話,它會調用一個read操作從磁盤中獲取頁面并且 阻塞線程直到磁盤操作完成。
- 當線程阻塞在硬盤操作的期間,為了完成更多的工作,調度線程可能挑選 另一個線程運行,也可能把另一個當前就緒的工作線程投入運行。
- 這種模型允許將服務器編寫為順序線程的集合,在分派線程的程序中包含一個死循環,該循環用來獲得 工作請求并且把請求派給工作線程。
- 每個工作線程的代碼包含一個從調度線程接收的請求,并且檢查 web高速緩存中是否存在所需頁面,如果有,直接把該頁面返回給客戶,接著工作線程阻塞,等待一個 新請求的到達。如果沒有,工作線程就從磁盤調入該頁面,將該頁面返回給客戶機,然后工作線程阻 塞,等待一個新請求。
下面是調度線程和工作線程的代碼,這里假設TRUE為常數1, buf和page分別是保存工作請求和 Web頁面的相應結構。
單線程解決方案
現在考慮沒有多線程的情況下,如何編寫Web服務器。
- 我們很容易的就想象為單個線程了,Web服務 器的主循環獲取請求并檢查請求,并爭取在下一個請求之前完成工作。
- 在等待磁盤操作時,服務器空 轉,并且不處理任何到來的其他請求。結果會導致每秒中只有很少的請求被處理
所以這個例子能夠說 明多線程提高了程序的并行性并提高了程序的性能。
狀態機解決方案
到現在為止,我們已經有了兩種解決方案,單線程解決方案和多線程解決方案,其實還有一種解決方案 就是狀態機解決方案,它的流程如下:
-
如果目前只有一個非阻塞版本的read系統調用可以使用,那么當請求到達服務器時,這個唯一的read 調用的線程會進行檢查
-
如果能夠從高速緩存中得到響應,那么直接返回,如果不能,則啟動一個非阻 塞的磁盤操作服務器在表中記錄當前請求的狀態
-
然后進入并獲取下一個事件,緊接著下一個事件可能就是一個新工 作的請求或是磁盤對先前操作的回答。
-
如果是新工作的請求,那么就開始處理請求。如果是磁盤的響 應,就從表中取出對應的狀態信息進行處理。
-
對于非阻塞式磁盤I/O而言,這種響應一般都是信號中斷 響應。
-
每次服務器從某個請求工作的狀態切換到另一個狀態時,都必須顯示的保存或者重新裝入相應的計算狀 態。
-
這里,每個計算都有一個被保存的狀態,存在一個會發生且使得相關狀態發生改變的事件集合,我 們把這類設計稱為有限狀態機(finite-state machine)
有限狀態機杯廣泛的應用在計算機科學 中。
- 這三種解決方案各有各的特性,多線程使得順序進程的思想得以保留下來,并且實現了并行性,但是順 序進程會阻塞系統調用;
- 單線程服務器保留了阻塞系統的簡易性,但是卻放棄了性能。有限狀態機的處 理方法運用了非阻塞調用和中斷,通過并行實現了高性能,但是給編程增加了困難。
2.經典的線程模型
理解進程的另一個角度是,用某種方法把相關的資源集中在一起。
- 進程有存放程序正文和數據以及其他 資源的地址空間。這些資源包括打開的文件、子進程、即將發生的定時器、信號處理程序、賬號信息等。把這些信息放在進程中會比較容易管理。
- 另一個概念是,進程中擁有一個執行的線程,通常簡寫為 線程(thread)。
- 線程會有程序計數器,用 來記錄接著要執行哪一條指令;線程還擁有寄存器,用來保存線程當前正在使用的變量;線程還會有堆 棧,用來記錄程序的執行路徑
- 盡管線程必須在某個進程中執行,但是進程和線程完完全全是兩個不同 的概念,并且他們可以分開處理。
- 進程用于把資源集中在一起,而線程則是CPU上調度執行的實體。
- 線程給進程模型增加了一項內容,即在同一個進程中,允許彼此之間有較大的獨立性且互不干擾。
- 在一 個進程中并行運行多個線程類似于在一臺計算機上運行多個進程。
- 在多個線程中,各個線程共享同一地 址空間和其他資源。在多個進程中,進程共享物理內存、磁盤、打印機和其他資源。
- 因為線程會包含有 一些進程的屬性,所以線程被稱為輕量的進程(lightweight processes)
- 多線程 (multithreading) 一詞還用于描述在同一進程中多個線程的情況。
下圖我們可以看到三個傳統的進程,每個進程有自己的地址空間和單個控制線程。
- 每個線程都在不同的 地址空間中運行
- 下圖中,我們可以看到有一個進程三個線程的情況。每個線程都在相同的地址空間中運行
- 線程不像是進程那樣具備較強的獨立性。同一個進程中的所有線程都會有完全一樣的地址空間,這意味 著它們也共享同樣的全局變量。
- 由于每個線程都可以訪問進程地址空間內每個內存地址,因此一個線程 可以讀取、寫入甚至擦除另一個線程的堆棧。
線程之間除了共享同一內存空間外,還具有如下不同的內 容
- 上圖左邊的是同一個進程中每個線程共享的內容,上圖右邊是每個線程中的內容。也就是說左邊的列 表是進程的屬性,右邊的列表是線程的屬性。
- 和進程一樣,線程可以處于下面這幾種狀態:運行中、阻塞、就緒和終止(進程圖中沒有畫)。
- 正在運 行的線程擁有CPU時間片并且狀態是運行中。一個被阻塞的線程會等待某個釋放它的事件。
- 例如,當 一個線程執行從鍵盤讀入數據的系統調用時,該線程就被阻塞直到有輸入為止。線程通常會被阻塞,直 到它等待某個外部事件的發生或者有其他線程來釋放它。線程之間的狀態轉換和進程之間的狀態轉換是 —樣的。
每個線程都會有自己的堆棧,如下圖所示:
線程系統調用
- 進程通常會從當前的某個單線程開始,然后這個線程通過調用一個庫函數(比如thread.create ) 創建新的線程。
- 線程創建的函數會要求指定新創建線程的名稱。創建的線程通常都返回一個線程標識 符,該標識符就是新線程的名字。
- 當一個線程完成工作后,可以通過調用一個函數(比如thread_exit )來退出
- 緊接著線程消失,狀 態變為終止,不能再進行調度。在某些線程的運行過程中,可以通過調用函數例如thread_join , 表示一個線程可以等待另一個線程退出
- 這個過程阻塞調用線程直到等待特定的線程退出。在這種情況 下,線程的創建和終止非常類似于進程的創建和終止。
- 另一個常見的線程是調用thread-yield ,它允許線程自動放棄CPU從而讓另一個線程運行。
- 這樣 一個調用還是很重要的,因為不同于進程,線程是無法利用時鐘中斷強制讓線程讓出CPU的。
3.POSIX線程
為了使編寫可移植線程程序成為可能,IEEE在IEEE標準1003.1c中定義了線程標準。
線程包被定義 為Pthreads .大部分的UNIX系統支持它。這個標準定義了 60多種功能調用,一一列舉不太現 實,下面為你列舉了一些常用的系統調用。
- POSIX線程(通常稱為pthreads)是一種獨立于語言而存在的執行模型,以及并行執行模型。
- 它允許程序控制時間上重疊的多個不同的工作流程。每個工作流程都稱為一個線程,可以通過調用 POSIX ThreadsAPI來實現對這些流程的創建和控制。可以把它理解為線程的標準。
- POSIX Threads的實現在許多類似且符合POSIX的操作系統上可用,例如FreeBSD、NetBSD、 OpenBSDs、Linux、macOS、Androids Solaris,它在現有 Windows API 之上實現了 pthreado
- IEEE是世界上最大的技術專業組織,致力于為人類的利益而發展技術👏👏👏。
- 所有的Pthreads都有特定的屬性,每一個都含有標識符、一組寄存器(包括程序計數器)和一組存儲 在結構中的屬性。這個屬性包括堆棧大小、調度參數以及其他線程需要的項目。
- 新的線程會通過pthread.create創建,新創建的線程的標識符會作為函數值返回。這個調用非常像 是UNIX中的fork系統調用(除了參數之外),其中線程標識符起著PID的作用,這么做的目的 是為了和其他線程進行區分。
- 當線程完成指派給他的工作后,會通過pthread.exit來終止。這個調用會停止線程并釋放堆棧。
- 一般一個線程在繼續運行前需要等待另一個線程完成它的工作并退出。可以通過pthread.join線程 調用來等待別的特定線程的終止。而要等待線程的線程標識符作為一個參數給出。
- 有時會出現這種情況:一個線程邏輯上沒有阻塞,但感覺上它已經運行了足夠長的時間并且希望給另外 —個線程機會去運行。這時候可以通過pthread_yield來完成。
下面兩個線程調用是處理屬性的。
- pthread-attr_init建立關聯一個線程的屬性結構并初始化成默 認值,這些值(例如優先級)可以通過修改屬性結構的值來改變。
- 最后,pthread_attr_destroy刪除一個線程的結構,釋放它占用的內存。 它不會影響調用它的線 程,這些線程會一直存在。
為了更好的理解pthread是如何工作的,考慮下面這個例子:
- 主線程在宣布它的指責之后,循環NUMBER_OF_THREADS次,每次創建一個新的線程。
- 如果線程創建 失敗,會打印出一條信息后退出。在創建完成所有的工作后,主程序退出。
4.線程實現
-
主要有三種實現方式
-
?在用戶空間中實現線程;
-
?在內核空間中實現線程;
-
?在用戶和內核空間中混合實現線程。
下面我們分開討論一下
在用戶空間中實現線程
- 第一種方法是把整個線程包放在用戶空間中,內核對線程一無所知,它不知道線程的存在。
所有的這類 實現都有同樣的通用結構:
- 線程在運行時系統之上運行,運行時系統是管理線程過程的集合,包括前面提到的四個過程: pthread_create,pthread_exit, pthreadjoin 和 pthread_yieldo
- 運行時系統(Runtime System)也叫做運行時環境,該運行時系統提供了程序在其中運行的環境。
- 此環境可能會解決許多問題,包括應用程序內存的布局,程序如何訪問變量,在過程之間傳遞參數的機制,與操作系統的接口等等。
- 編譯器根據特定的運行時系統進行假設以生成正確的代碼。
通常,運行時系統將負責設置和管理堆棧,并且會包含諸如垃圾收集,線程或語言內置的其 他動態的功能。
-
在用戶空間管理線程時,每個進程需要有其專用的線程表(thread table),用來跟蹤該進程中的線 程。
-
這些表和內核中的進程表類似,不過它僅僅記錄各個線程的屬性,如每個線程的程序計數器、堆棧 指針、寄存器和狀態。
-
該線程標由運行時系統統一管理。當一個線程轉換到就緒狀態或阻塞狀態時,在 該線程表中存放重新啟動該線程的所有信息,與內核在進程表中存放的信息完全一樣。
在用戶空間實現線程的優勢
-
在用戶空間中實現線程要比在內核空間中實現線程具有這些方面的優勢:
-
考慮如果在線程完成時或者是 在調用pthread_yield時,必要時會進程線程切換,然后線程的信息會被保存在運行時環境所提供 的線程表中,然后,線程調度程序來選擇另外一個需要運行的線程。
-
保存線程的狀態和調度程序都是本 地過程,所以啟動他們比進行內核調用效率更高。因而不需要切換到內核,也就不需要上下文切換,也不需要對內存高速緩存進行刷新,因為線程調度非常便捷,因此效率比較高。
-
在用戶空間實現線程還有一個優勢就是它允許每個進程有自己定制的調度算法。
- 例如在某些應用程序 中,那些具有垃圾收集線程的應用程序(知道是誰了吧)就不用擔心自己線程會不會在不合適的時候停 止,這是一個優勢。
- 用戶線程還具有較好的可擴展性,因為內核空間中的內核線程需要一些表空間和堆 棧空間,如果內核線程數量比較大,容易造成問題。
在用戶空間實現線程的劣勢
盡管在用戶空間實現線程會具有一定的性能優勢,但是劣勢還是很明顯的,你如何實現阻塞系統調用 呢?
- 假設在還沒有任何鍵盤輸入之前,一個線程讀取鍵盤,讓線程進行系統調用是不可能的,因為這會 停止所有的線程。
- 所以,使用線程的一個目標是能夠讓線程進行阻塞調用,并且要避免被阻塞的線程影 響其他線程。
- 與阻塞調用類似的問題是缺頁中斷問題,實際上,計算機并不會把所有的程序都一次性的放入內存中,如果某個程序發生函數調用或者跳轉指令到了一條不在內存的指令上,就會發生頁面故障,而操作系統將到磁盤上取回這個丟失的指令,這就稱為缺頁故障。
- 而在對所需的指令進行讀入和執行時,相 關的進程就會被阻塞。如果只有一個線程引起頁面故障,內核由于甚至不知道有線程存在,通常會吧整個進程阻塞直到磁盤I/O完成為止,盡管其他的線程是可以運行的。
- 另外一個問題是,如果一個線程開始運行,該線程所在進程中的其他線程都不能運行,除非第一個線程自愿的放棄CPU,在一個單進程內部,沒有時鐘中斷,所以不可能使用輪轉調度的方式調度線程。除非其他線程能夠以自己的意愿進入運行時環境,否則調度程序沒有可以調度線程的機會。
5.在內核中實現線程
-
現在我們考慮使用內核來實現線程的情況,此時不再需要運行時環境了。另外,每個進程中也沒有線程 表。
-
相反,在內核中會有用來記錄系統中所有線程的線程表。當某個線程希望創建一個新線程或撤銷一 個已有線程時,它會進行一個系統調用,這個系統調用通過對線程表的更新來完成線程創建或銷毀工 作。
-
內核中的線程表持有每個線程的寄存器、狀態和其他信息。這些信息和用戶空間中的線程信息相同,但是位置卻被放在了內核中而不是用戶空間中。另外,內核還維護了一張進程表用來跟蹤系統狀態。
-
所有能夠阻塞的調用都會通過系統調用的方式來實現,當一個線程阻塞時,內核可以進行選擇,是運行在同一個進程中的另一個線程(如果有就緒線程的話)還是運行一個另一個進程中的線程。
-
但是在用戶 實現中,運行時系統始終運行自己的線程,直到內核剝奪它的CPU時間片(或者沒有可運行的線程存 在了)為止。
-
由于在內核中創建或者銷毀線程的開銷比較大,所以某些系統會采用可循環利用的方式來回收線程。
-
當 某個線程被銷毀時,就把它標志為不可運行的狀態,但是其內部結構沒有受到影響。
-
稍后,在必須創建 一個新線程時,就會重新啟用舊線程,把它標志為可用狀態。
-
如果某個進程中的線程造成缺貞故障后,內核很容易的就能檢查出來是否有其他可運行的線程,如果有的話,在等待所需要的頁面從磁盤讀入時,就選擇一個可運行的線程運行。這樣做的缺點是系統調用的代價比較大,所以如果線程的操作(創建、終止)比較多,就會帶來很大的開銷。
6.混合實現
結合用戶空間和內核空間的優點,設計人員采用了一種內核級線程的方式,然后將用戶級線程與某些或者全部內核線程多路復用起來:
- 在這種模型中,編程人員可以自由控制用戶線程和內核線程的數量,具有很大的靈活度。
- 采用這種方 法,內核只識別內核級線程,并對其進行調度。其中一些內核級線程會被多個用戶級線程多路復用。
總結
- 上一篇: OS- -进程详详解
- 下一篇: OS--进程间通信详解(一)