天天看點

作業系統概念學習筆記 9 線程作業系統概念學習筆記 9

單個程序可以包括多個控制線程。

線程 ——一種CPU利用的基本單元,它是形成多線程計算機的基礎。

線程是CPU使用的基本單元,它由線程ID、程式計數器、寄存器集合和棧組成。它與屬于統一程序的其他線程共享代碼段、資料段和其他作業系統資源。

一個傳統重量級的程序隻有單個控制線程,如果程序有多個控制線程,那麼能同時做多個任務。

作業系統概念學習筆記 9 線程作業系統概念學習筆記 9

單線程與多線程

一個應用程式通常是作為一個具有多個控制線程的獨立程序實作的。如一個忙碌的網頁伺服器如果有多個(或數千個)客戶并發通路它,可以建立多個線程而非程序,因為建立程序很耗時間,而單個線程的程序一次隻能處理一個請求。

線程在遠端過程調用(RPC)系統中個也有很重要的作用,通常,PRC伺服器是多線程的。當一個伺服器接收到消息,它使用獨立線程處理消息,這允許伺服器能處理多個并發請求。

現代許多作業系統都是多線程的,少數線程在核心中運作,每個線程完成一個指定的任務。

響應度高:即使其部分阻塞或執行較冗長操作,該程式仍能繼續執行,進而增加了對使用者的相應程度。

資源共享:線程預設共享它們所屬程序的記憶體和資源。代碼和資料共享的優點是它允許一個應用程式在同一位址空間有多個不同的活動線程。

經濟:程序建立所需要的記憶體和資源的配置設定比較昂貴。由于線程能共享它們所屬程序的資源,是以建立和切換線程會更為經濟。

多處理器體系結構的利用:多線程的優點之一是能充分使用多處理器體系結構。以便每個程序能并行運作在不同的處理器上。不管有多少CPU,單線程程序隻能運作在一個CPU上,在多CPU上使用多線程加強了并發功能。

有兩種不同的方法來提供線程支援:使用者層的使用者線程和核心層的核心線程。使用者線程受核心支援,而無需核心管理;而核心線程由作業系統支援和管理。事實上所有當代作業系統都支援核心線程。在使用者線程和核心線程之間必然存在一種關系。

多對一模型将許多使用者級線程映射到一個核心線程。線程管理由線程庫在使用者空間進行的,因而效率比較高。但是如果一個線程執行了阻塞系統調用,那麼整個線程會阻塞。因為任意時刻隻能有一個線程能夠通路核心,多個線程不能并行運作在多處理器上。

作業系統概念學習筆記 9 線程作業系統概念學習筆記 9

一對一模型每個使用者線程映射到一個核心線程。該模型在一個線程執行阻塞系統調用時,能允許另一個線程繼續執行。它也允許多個線程能并行運作在多處理器系統上,這種模型的唯一缺點是每建立一個使用者線程就會建立一個相應的核心線程。由于建立核心線程的開銷會影響應用程式的性能,是以這種模型的絕大多數實作限制了作業系統所支援的線程數量。

作業系統概念學習筆記 9 線程作業系統概念學習筆記 9

多對多模型多路複用了許多使用者線程到同樣數量或更小數量的核心線程上。多對多模型沒有這兩者的缺點:開發人員可建立任意多的使用者線程,并且相應核心線程能在多處理器系統上并發執行。而且當一個線程執行阻塞系統調用時,核心能排程另一個線程來執行。

一個流行多對多模型的變種仍然多路服用了許多使用者線程到同樣數量或更小數量的核心線程上,但也允許将一個使用者線程綁定到某個核心線程上。這個變種有是被稱為二級模型。

作業系統概念學習筆記 9 線程作業系統概念學習筆記 9
作業系統概念學習筆記 9 線程作業系統概念學習筆記 9

thread library):為程式員提供建立和管理線程的API。主要有兩種方法來實作線程庫。

(1)在使用者空間中提供一個沒有核心支援的庫,此庫的所有代碼和資料結構都存在于使用者空間中。調用庫中的一個函數知識導緻使用者空間中一個本地函數調用,而不是系統調用。

(2)執行一個有作業系統直接支援的核心級的庫。此時,庫的代碼和資料結構存在于核心空間中。調用庫中的一個API函數通常會導緻對核心的系統調用。

目前使用的三種主要的線程庫是:

(1)POSIX Pthread

(2)Win32

(3)Java

Pthread作為POSIX标準擴充,可以提供使用者級或核心級的庫。Win32線程庫是适用于Windows作業系統的核心級線程庫。Java線程API允許線程在java程式中直接建立和管理。然而,由于大多數JVM執行個體運作在宿主作業系統之上,Java線程API通常采用宿主系統上的線程庫來實作。

Pthread是由POSIX标準為線程建立和同步定義的API。這是線程行為的規範,而不是實作。作業系統設計者可以根據意願采取任何形式來實作。

所有Pthread程式都需要包括pthread.h頭檔案。

Pthread_t tid聲明了所常見線程的辨別符。每個線程都有一組屬性,包括棧大小和排程資訊。

Pthread_attr_t attr表示線程的屬性,通過函數調用pthread_attr_init(&attr)來設定這些屬性。

Pthread_create()建立一個獨立線程。除了傳遞線程辨別符和線程屬性外,還要傳遞函數名稱。

pthread_join()函數等待

pthread_exit()完成

Pthread執行個體程式:

以下示例實驗程式實作并發的兩個線程合作将整數 X 的值從 1 加到 10 的功

能。它們通過管道互相将計算結果發給對方。

運作結果:

Win32線程庫建立線程的技術在某些方面類似與Pthread技術。

Win32線程:Win32 API必須包括windows.h頭檔案

線程的建立使用了CreateThread() 将一組線程的屬性傳遞給此函數。

在Pthread程式中采用的pthread_join()語句實作線程等待,在Win32中采用同等功能的函數WaitForSingleObject(),進而使建立者線程阻塞。

Java線程:線程是Java程式中程式執行的基本模型。

所有Java程式至少有一個控制線程組成,即使是一個隻有main()函數的簡單Java程式也是在JVM中作為一個線程運作的。

Java程式中有兩種建立線程的技術。

(1)建立一個新的類,它從Thread類派生,并重載它的run()函數。

(2)定義一個實作Runnable()接口的類。當一個類執行runnable()時,他必須定義run()函數,而實作run()函數的代碼被作為一個獨立的線程執行。

建立Thread對性并不會建立一個新的線程,實際上是用start()函數來建立新線程。為新的對象調用start()函數需要做兩件事:

一是在JVM配置設定記憶體并初始化新的線程;

二是調用run()函數,實作線程适合在JVM中運作

(注意,從不直接調run()函數,而是調用start()函數,然後它再調用run()函數)。

與pthread_join()相對應的java中有join()函數。

三者比較:

在Win32和Pthread共享資料很友善,可以将共享資料簡單的聲明為全局資料。而對于Java沒有全局資料的概念,在Java程式中如果兩個或更多的線程需要共享資料,通過向相應的線程傳遞對共享對象的引用來實作。

在多線程程式中,系統調用fork()和exec()的語義有所改變。

如果程式中一個程序調用fork(),那麼新程序會複制所有線程,還是新程序隻有單個線程?有的UNIX系統有兩種形式的fork(),一種複制所有線程,另一種隻複制調用了系統調用fork()的線程。

Exec()工作方式:如果一個線程調用系統調用exec(),那麼exec()參數所指定的程式會替換整個程序,包括所有線程。

如果調用fork()之後立即調用exec(),那麼沒有必要複制所有線程,因為exec()參數所指定的程式會替換整個程序。在這種情況下,隻複制調用線程比較适當。不過,如果在fork()之後另一程序并不調用exec(),那麼另一程序就應複制所有程序。

線程取消(thread cancellation)是線上程完成之前來終止線程的任務。

要取消的線程通常稱為目标線程。目标線程的取消可在如下兩種情況下發生:

一是異步取消(asynchronous cancellation):一個線程立即終止目标線程。

二是延遲取消(deferred cancellation):目标線程不斷地檢查它是否應終止,這允許目标線程有機會以有序方式來終止自己。

如果資源已經配置設定給要取消的線程,或者要取消的線程正在更新與其他線程所共享的資料,那麼取消會有困難,對于異步取消尤為麻煩。作業系統回收取消線程的系統資源,但是通常不回收所有資源。是以,異步取消線程并不會使所需的系統資源空閑。相反采用延遲取消時,允許一個線程檢查它是否是在安全的點被取消,pthread稱這些點為取消點(cancellation point)

信号處理:信号在Unix中用來通知程序某個特定時間已發生了,信号可以同步或異步接收。所有有信号具有同樣的模式:

(1)信号有特定事件的發生所産生

(2)産生的信号要發送到程序

(3)一旦發送,信号必須交易處理。

同步信号的例子包括通路非法記憶體或被0除。在這種情況下,如果運作程式執行這些動作,那麼就産生信号,同步信号發送到執行操作而産生信号的同一程序(同步的原因)。

當一個信号由運作程序之外的事件産生,那麼程序就異步接收這一信号。這種信号的例子包括使用特殊鍵(Ctrl + C)或者定時器到期。通常,異步信号被發送到另一個程序。

每個信号可能由兩種可能的處理程式中的一種來處理:

(1)預設信号處理程式

(2)使用者定義的信号處理程式

每個信号都有一個預設信号處理程式,當處理信号是在核心中運作的,這種預設動作可以用使用者定義的信号處理程式來改寫。信号可以按照不同的方式處理。有的信号可以簡單的忽略(如改變視窗大小),有的需要終止程式來處理(非法記憶體通路)

單線程程式的信号處理比較直接,信号總是發送給程序。

當多線程時,信号會

(1)發送信号到信号所應用的線程

(2)發送信号到程序内的每個線程

(3)發送信号到程序内的某些固定線程

(4)規定一個特定線程以接收程序的所有信号。

發送信号的方法依賴于信号的類型。

大多數線程版Unix允許線程描述它會接收什麼信号和拒絕什麼信号。,因為信号隻能處理一次,是以信号通常發送到不拒絕它的第一個線程。

pthread還提供了pthread_kill函數,此函數允許限号被傳送到一個指定線程。

Windows,通過異步過程調用(asynchronous procedure call,APC)來模拟信号。

多線程伺服器有一些潛在問題:第一個是關于處理請求之前用以建立線程的時間,以及線程在完成工作之後就要被丢棄這一事實。第二個,如果允許所有并發請求都通過新線程來處理,那麼将沒法限制在系統中并發執行的線程的數量。無限制的線程會耗盡系統資源。解決這一問題是使用線程池。

線程池的思想是在程序開始時建立一定數量的線程,并放入到池中以等待工作。當伺服器收到請求時,他會喚醒池中的一個線程,并将要處理的請求傳遞給他,一旦線程完成了服務,它會傳回到池中在等待工作。如果池中沒有可用的線程,那麼伺服器會一直等待直到有空線程為止。

線程池的優點:

(1)通常用現有線程處理請求要比等待建立新的線程要快

(2)線程池限制了在任何時候可用線程的數量。

線程池中的線程數量由系統CPU的數量、實體記憶體的大小和并發客戶請求的期望值等因素決定。比較進階的線程池能動态的調整線程的數量,以适應具體情況。

同屬一個程序的線程共享程序資料。

在某些情況下每個線程可能需要一定資料的自己的副本,這種資料稱為線程特定資料。可以讓每個線程與其唯一的辨別符相關聯。

核心與線程庫的通信問題,就是要讨論多對多模型。這種協調允許動态調整核心線程的數量以保證其最好的性能。

在使用者核心線程之間設定一種中間資料結構。輕量級程序(LWP),他表現為一種應用程式可以排程使用者線程來運作的虛拟處理器。每個LWP與核心線程相連,該核心線程被作業系統排程到實體處理器上運作,如果核心線程阻塞,LWP阻塞,與LWP相連的使用者線程也阻塞。如果在單處理器上,一次隻能運作一個線程,則隻需要一個LWP就夠了,但I/O密集的應用程式可能需要多個LWP來執行。

一種結局使用者線程庫與核心間通信的方法為排程器激活(scheduler activation),核心提供一組虛拟處理器(LWP)給應用程式,應用程式可排程使用者程序到一個可用的虛拟處理器上。