作業系統:多執行緒

Mr.Sen發表於2020-12-17

一、概述

用pthread_create() 建立執行緒
相對建立程式,建立執行緒比較簡單,只需要複製堆疊和暫存器的內容

執行緒是執行在應用程式裡的、比程式更小的執行單位
一個應用程式通常作為具有多個控制執行緒的一個程式實現。

每個執行緒是獨立的排程物件。 一個程式可以擁有單個或多個執行緒,並共享記憶體空間。

一個程式可以擁有多個執行緒,而且執行緒之間共享以下內容

  1. 程式碼段,全域性變數
  2. 開啟的檔案識別符號
  3. 工作環境,包括當前目錄,使用者許可權等

執行緒的執行是程式內部的執行軌跡,是程式內部執行指令的跟蹤。

多執行緒程式設計優點

  1. 響應速度快:即使部分執行緒發生阻塞,其他執行緒可以繼續執行
  2. 資源共享:共享所屬程式的記憶體和資源(允許一個應用程式在同一地址空間內有多個不同活動的執行緒)
  3. 經濟:建立和切換執行緒更經濟
  4. 多處理器體系結構的利用(可伸縮性):多執行緒可以在多處理器上並行執行

二、多執行緒模型

有兩種不同的方法來提供執行緒支援。
使用者執行緒

  1. 使用者執行緒受核心的支援
  2. 使用者執行緒是由使用者層的執行緒庫來管理,無需核心管理
  3. 位於核心之上

核心執行緒

  1. 核心執行緒是由作業系統來直接支援和管理
  2. 由核心來進行維護和排程

使用者執行緒和核心執行緒必然存在某種關係。有一對一、多對一、多對多模型,三種方法建立起來的這種關係,我們稱為對映

討論它們之間關係之前,先做以下假設

  1. 作業系統支援核心執行緒
  2. 把核心執行緒看成是一個程式
  3. 使用者執行緒基於核心執行緒執行,即使用者執行緒阻塞核心阻塞、核心執行緒阻塞使用者執行緒也阻塞(被阻塞的執行緒會進入到等待狀態)。

1.一對一模型
一對一模型對映一個使用者執行緒到一個核心執行緒

優點:

  1. 可以提供併發功能,可以在多個處理器上並行執行
  2. 在一個執行緒執行阻塞系統呼叫時,可以排程另一個執行緒繼續執行

缺點:

  1. 建立核心執行緒的開銷會影響應用程式效能

2.多對一模型
多對一模型對映多個使用者執行緒到一個核心執行緒

缺點:

  1. 一個使用者執行緒一但被阻塞,其他使用者執行緒也會阻塞,整個程式被阻塞
  2. 只有一個執行緒訪問核心,不能並行執行

3.多對多模型
多對多模型多路複用多個使用者執行緒到同樣數量或更少數量的核心執行緒
多對多模型沒有以上缺點,當一個執行緒執行阻塞系統呼叫時,核心能排程另一個執行緒的執行,但需要提供排程機制

二級模型/雙層模型(混合模型)
多對多模型的變種,仍然是多對多的模型,但也允許使用者執行緒繫結一個特定的核心執行緒

三、執行緒庫

執行緒庫為程式設計師提供建立和管理執行緒的API

實現執行緒庫的兩種方法

  1. 非嵌入到核心的方式
    在使用者空間中提供一個沒有核心支援的庫,此庫的所有程式碼和資料結構都存在於使用者空間中
  2. 嵌入到核心的方式
    由作業系統直接支援的核心庫,此時所有程式碼和資料結構都存在於核心空間中

目前使用的三種主要執行緒庫:
3. POSIX Pthread: 提供使用者級或核心級的庫
4. Windows:核心級庫,嵌入到核心的方式
5. Java:Java 執行緒API通常採用宿主系統上的執行緒庫來實現的

四、多執行緒問題

1.系統呼叫fork()和exec()

如果程式中的某一個執行緒呼叫fork(),那麼新程式會複製所有執行緒,或者新程式只有單個程式?
答:有的Unix 系統提供兩種形式的fork()系統呼叫,一種複製所有執行緒,一種僅僅複製呼叫了系統呼叫fork()的執行緒。

這兩種形式的fork()使用取決於應用程式。如果分叉之後立刻呼叫exec(),那麼沒必要複製所有執行緒,因為exec()引數指定的程式會代替整個程式,這種情況下僅複製呼叫執行緒比較適合。不過如果新程式在分叉後並不呼叫exec(),新程式應該重複所有執行緒。
如呼叫exec(),就複製一個;如不呼叫exec(),就複製全部

2.執行緒撤銷
執行緒撤銷是線上程完成之前終止執行緒

需要取消的執行緒一般稱為目標執行緒

目標執行緒的撤銷有兩種情況:

  1. 非同步撤銷(Asynchronous Cancellation):一個執行緒立即終止目標執行緒
  2. 延遲撤銷(Deferred Cancellation):目標執行緒不斷地檢查它是否應終止,這允許目標執行緒有機會有序終止自己

目標執行緒的取消可能發生“要取消的執行緒正在更新與其他執行緒所共享的資料”或“已分配資源給撤銷執行緒”,撤銷會變得困難,對於非同步撤銷,這尤為麻煩。所以非同步撤銷執行緒可能不會釋放必要的系統資源

相反對於延遲撤銷,一個執行緒指示目標執行緒撤銷,但僅當目標執行緒檢查到一個標誌以確定她是否應該被撤銷時,撤銷才會發生

對於Pthreads,呼叫pthread——cancle()發起執行緒撤銷,不過這隻表示一個請求,執行緒取消請求的實際取消的行為依賴於執行緒的狀態

Pthreads支援三種撤銷模式(狀態):

  1. 關閉: 如果系統設定為禁用執行緒取消的話,執行緒取消會待定直到可以取消執行緒為止
  2. 延遲: 預設狀態,延遲取消
  3. 非同步: 非同步模式,立即終止目標執行緒

延遲撤銷只有當執行緒到達撤銷點,才會發生撤銷。
撤銷點(Cancellation Point):當一個執行緒認定為可以安全取消時,可以安全取消的這個點稱為取消點, 即pthread_testcancel() 會被呼叫。

3.訊號處理
在Linux系統中,執行緒取消是通過訊號來處理的。
訊號是用來通知程式某個特定事件的發生,這需要作業系統提供一種核心和程式之間的通訊機制

訊號的接收可以是同步或非同步的,這取決於訊號的來源和原因

訊號有以下兩種:

  1. 同步訊號(內部訊號):程式本身事件產生的訊號,如訪問非法記憶體、除零等
  2. 非同步訊號(外部訊號):程式之外事件產生的訊號,如按CTRL+C鍵等

訊號處理程式是用來處理髮生的事件,處理過程如下:
• 由特定事件產生訊號
• 這個訊號傳送給程式
• 訊號處理程式處理相應訊號

訊號處理程式有預設訊號處理程式使用者定義的訊號處理程式

事件產生訊號,傳送給程式需要考慮的問題?

  • 對於單執行緒程式,沒什麼問題
  • 對於多執行緒程式,應該傳送給程式的哪個執行緒
    I. 傳送訊號到訊號所適用的執行緒
    II. 傳送訊號到程式內每個執行緒
    III.傳送訊號到程式某些執行緒
    IV. 規定一個特定的執行緒以接收程式的所有訊號

4.執行緒池
執行緒池(thread pool)的主要思想是在程式開始時建立一定數量的執行緒,並放入到程式池中等待工作

優點

  1. 通常用現有執行緒處理請求要比等待建立新的執行緒要快
  2. 執行緒池限制了在任何時候可用執行緒的數量,這可以防止大量建立併發執行緒,有助於管理

5.排程程式啟用
排程程式啟用:一種解決使用者執行緒庫和核心間通訊的方法

之前討論的多對多模型或雙層模型可能需要這種通訊,這種協調允許動態調整核心執行緒數量,以便保證最優效能。

許多系統在實現多對多模型或雙層模型時,使用者和核心執行緒之間設定一種中間資料結構,這種資料結構通常稱為輕量級程式(LWP:LightWeight Process)

對於使用者執行緒庫,LWP表現為一種應用程式可以排程使用者執行緒的虛擬處理器

每個LWP與核心執行緒相連,只有核心執行緒才能通過作業系統排程在物理處理器上執行。如果核心執行緒阻塞,LWP也阻塞,相連使用者執行緒也會阻塞

使用者執行緒庫和核心之間的一種通訊方案稱為排程器啟用,工作如下:核心提供一組LWP給應用程式,應用程式可排程使用者執行緒到一個可用的LWP上

核心將特定事件通知應用程式,這個步驟叫做回撥,由執行緒庫通過回撥處理程式來處理

參考

《作業系統概念》

相關文章