作業系統——深入理解程式和執行緒

it_was發表於2020-09-16

程式:是執行中一段程式,一個程式被載入到記憶體中並準備執行,它就是一個程式,是系統進行資源分配和排程的一個基本單位。

執行緒:是程式的一個實體,是cpu排程和分派的基本單位,它是比程式更小的能獨立執行的基本單位,有時被稱為輕量級程式。

  1. 同一個程式可以包含多個執行緒,一個程式中至少包含一個執行緒,一個執行緒只能存在於一個程式中。即執行緒必須依託於程式。

  2. 同一程式下的各個執行緒並不是互相獨立的,需要共享程式的資源。而各個程式基本上獨立的,並不互相干擾。

  3. 執行緒是輕量級的程式,它的建立和銷燬所需要的時間和資源消耗相比程式比程式小得多

  4. 在作業系統中,程式是擁有系統資源分配和排程的獨立單元,它可以擁有自己的資源。一般而言,執行緒不能擁有自己的資源,但是它能夠訪問其隸屬程式的資源,執行緒是CPU分派和排程的基本單位

3.1 概述

之前說過,程式間是相互獨立的,但是有時候為了共同完成一組任務需要各個程式相互合作,這就需要各個程式之間進行資料傳輸或者資源共享,因此需要程式間通訊(IPC Interprocess Communication)

3.2 目的

  1. 資料傳輸:各個程式之間需要交換傳輸資料
  2. 共享資料:各個程式需要操作共享資料,一個程式對其修改別的程式應該立馬看到
  3. 通知事件:一個程式需要向另一個或一組程式傳送訊息,通知它(它們)發生了某種事件(如程式終止時要通知父程式)。
  4. 程式控制:有些程式希望完全控制另一個程式的執行(如Debug程式),此時控制程式希望能夠攔截另一個程式的所有陷入和異常,並能夠及時知道它的狀態改變。

3.3 方式-七種

1.管道/匿名管道(pipe)

作業系統——深入理解程式和執行緒

特點

  • 半雙工
  • 親緣關係
  • 單獨構成一種檔案系統,並且只存在與記憶體中。
  • 佇列形式的讀寫(FIFO)

缺點(從特點上說!)

  • 半雙工
  • 親緣關係
  • 存於記憶體,大小受限
  • 無格式位元組流,這就要求管道的讀出方和寫入方必須事先約定好資料的格式,比如多少位元組算作一個訊息(或命令、或記錄)等

2.有名管道

哦,貌似加了名字(準確來說是將一個路徑名與之關聯)!這就是最主要的區別,這樣不同程式即使無親緣關係也可以互相通訊啦!其他都一樣~ 強調一下有名管道的名字存在於檔案系統中,內容存放在記憶體中。

3.訊號(signal)

是Unix系統中使用的最古老的程式間通訊的方法之一。作業系統透過訊號來通知程式系統中發生了某種預先規定好的事件(一組事件中的一個),它也是使用者程式之間通訊和同步的一種原始機制。

作業系統——深入理解程式和執行緒
訊號的生命週期

Linux系統中常用訊號:
(1)SIGHUP:使用者從終端登出,所有已啟動程式都將收到該程式。系統預設狀態下對該訊號的處理是終止程式。
(2)SIGINT:程式終止訊號。程式執行過程中,按Ctrl+C鍵將產生該訊號。
(3)SIGQUIT:程式退出訊號。程式執行過程中,按Ctrl+\\鍵將產生該訊號。
(4)SIGBUS和SIGSEGV:程式訪問非法地址。
(5)SIGFPE:運算中出現致命錯誤,如除零操作、資料溢位等。
(6)SIGKILL:使用者終止程式執行訊號。shell下執行kill -9傳送該訊號。
(7)SIGTERM:結束程式訊號。shell下執行kill 程式pid傳送該訊號。
(8)SIGALRM:定時器訊號。
(9)SIGCLD:子程式退出訊號。如果其父程式沒有忽略該訊號也沒有處理該訊號,則子程式退出後將形成殭屍程式。

程式可以對任何訊號指定另一個動作或過載預設動作,指定的新動作可以是忽略訊號。程式也可以暫時地阻塞一個訊號。因此程式可以選擇對某種訊號所採取的特定操作,這些操作包括:

  • 忽略訊號:程式可忽略產生的訊號,但 SIGKILL 和 SIGSTOP 訊號不能被忽略,必須處理(由程式自己或由核心處理)。程式可以忽略掉系統產生的大多數訊號。
  • 阻塞訊號:程式可選擇阻塞某些訊號,即先將到來的某些訊號記錄下來,等到以後(解除阻塞後)再處理它。
  • 由程式處理該訊號:程式本身可在系統中註冊處理訊號的處理程式地址,當發出該訊號時,由註冊的處理程式處理訊號。
  • 由核心進行預設處理:訊號由核心的預設處理程式處理,執行該訊號的預設動作。例如,程式接收到SIGFPE(浮點異常)的預設動作是產生core並退出。大多數情況下,訊號由核心處理。

4.訊息佇列(message queue)

訊息佇列,是訊息的連結表,存放在核心中。一個訊息佇列由一個識別符號(即佇列ID)來標識。

特點
  • 訊息佇列可以實現訊息的隨機查詢,訊息不一定要以先進先出的次序讀取,也可以按訊息的型別讀取.比FIFO更有優勢。
  • 訊息佇列克服了訊號承載資訊量少,管道只能承載無格式字 節流以及緩衝區大小受限等缺。

5.共享記憶體(share memory)

作業系統——深入理解程式和執行緒

特點

  • 直接讀寫同一塊記憶體
  • 地址對映,無需複製
  • 需要同步機制來確保共享記憶體的安全性

6.訊號量(semaphore)

訊號量是一個計數器,用於多程式對共享資料的訪問,訊號量的意圖在於程式間同步。就是作業系統之前提到過的 P V 原語操作(生產者消費者!)

訊號量與互斥量之間的區別:
(1)首先互斥量用於執行緒的互斥,訊號量用於執行緒的同步。這是互斥量和訊號量的根本區別,也就是互斥和同步之間的區別。
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
同步:是指在互斥的基礎上(大多數情況),透過其它機制實現訪問者對資源的一定順序的訪問。可以透過訊號量實現!
(2)互斥量值只能為0/1,訊號量值可以為非負整數。
一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多執行緒互斥問題。訊號量可以實現多個同類資源的多執行緒互斥和同步。當訊號量為單值訊號量時,也可以完成一個資源的互斥訪問。

7.套接字(socket)

  1. 互斥量(Mutex):採用互斥物件機制,只有擁有互斥物件的執行緒才有訪問公共資源的許可權。因為互斥物件只有一個,所以可以保證公共資源不會被多個執行緒同時訪問。比如 Java 中的 synchronized 關鍵詞和各種 Lock 都是這種機制。
  2. 訊號量(Semphares) :它允許同一時刻多個執行緒訪問多個同類資源,但是需要控制同一時刻訪問此資源的最大執行緒數量
  3. 事件(Event) :Wait/Notify:透過通知操作的方式來保持多執行緒同步,還可以方便的實現多執行緒優先順序的比較操

** 主流的作業系統都提供了執行緒的實現**,注意這句話,誰實現的執行緒?是作業系統,實際上實現執行緒的老大哥,是執行在核心態的作業系統。

6.1作業系統實現執行緒主要有 3 種方式

  • 使用者級執行緒(非主流)
  • 核心級執行緒(主流)
  • 使用者級執行緒 + 核心級執行緒,混合實現(非主流)

6.2核心級執行緒

核心執行緒就是直接由作業系統核心支援的執行緒,這種執行緒由核心來完成執行緒切換,核心透過操縱排程器對執行緒進行排程,並負責將執行緒的任務對映到各個處理器上。每個核心執行緒可以視為核心的一個分身。

使用者程式一般不會直接去使用核心執行緒,而是去使用核心執行緒的一種高階介面——輕量級程式(Light Weight Process,LWP),輕量級程式就是我們通常意義上所講的執行緒,由於每個輕量級程式都由一個核心執行緒支援,因此只有先支援核心執行緒,才能有輕量級程式。這種輕量級程式與核心執行緒之間 1 : 1 的關係稱為一對一的執行緒模型,如下圖所示。

在這裡插入圖片描述
由於有核心執行緒的支援,每個輕量級程式都成為一個獨立的排程單元,即使有一個輕量級程式在系統呼叫中阻塞了,也不會影響整個程式繼續工作,但是輕量級程式具有它侷限性,主要有如下兩點

  • 執行緒的建立、銷燬等操作,都需要進行系統呼叫,而系統呼叫的代價相對較高,需要在使用者態和核心態之間來回切換。
  • 每個輕量級程式都需要有一個核心執行緒的支援,因此輕量級程式要消耗一定的核心資源(比如核心執行緒的棧空間),因此一個系統支援輕量級執行緒的數量是有限的。

6.2使用者級執行緒

使用者級執行緒的實現就是把整個執行緒實現部分放在使用者空間中,核心對執行緒一無所知,核心看到的就是一個單執行緒程式。

對於執行緒的底層實現,現在很少有作業系統會使用單純的使用者級執行緒這中執行緒模型來實現。

注:對於實現的是使用者級執行緒的作業系統而言,CPU 排程的基本單位看起來像是程式(因為在核心看來,這些程式都是單執行緒的,所以對單執行緒的排程就像是在排程程式一樣)。

在這裡插入圖片描述

使用使用者執行緒的優勢在於不需要核心支援,劣勢也在於沒有核心的支援,所有的執行緒操作都需要使用者程式自己處理。執行緒的建立、切換和排程都是需要考慮的問題。因而使用使用者執行緒實現的程式都比較複雜,除了以前在不支援多執行緒的作業系統中的多執行緒程式與少數有特殊需求的程式外,現在使用使用者執行緒的程式越來越少了。

6.3二者的對比

關於使用者級執行緒和核心級執行緒這兩種執行緒模型的對比,個人認為主要可以從排程、開銷、效能這三個角度來看待。

  • 排程:對於使用者級執行緒,作業系統核心不可感知,排程需要由開發者自己實現,核心級執行緒則與之相反,開發者可以做個甩手掌櫃,將排程全權交由作業系統核心來完成。
  • 開銷:在前面介紹使用者級執行緒的優點時,也提到了,在使用者空間建立執行緒的開銷相比之下會比核心空間小很多。
  • 效能:使用者級執行緒的切換髮生在使用者空間,這樣的執行緒切換至少比陷入核心要快一個數量級,不需要陷入核心、不需要上下文切換、不需要對記憶體快取記憶體進行重新整理,這就使得執行緒排程非常快捷。

在早期的作業系統中有不支援執行緒的,都是使用使用者執行緒來實現的,現在都支援執行緒了,大多數都使用輕量級程式去對映核心執行緒的手段來實現多執行緒技術,包括常見的 Windows 和 Linux 就這種一對一的執行緒模型。

推薦閱讀:上下文切換

  • 先到先服務(FCFS)排程演算法 : 從就緒佇列中選擇一個最先進入該佇列的程式為之分配資源,使它立即執行並一直執行到完成或發生某事件而被阻塞放棄佔用 CPU 時再重新排程。
  • 短作業優先(SJF)的排程演算法 : 從就緒佇列中選出一個估計執行時間最短的程式為之分配資源,使它立即執行並一直執行到完成或發生某事件而被阻塞放棄佔用 CPU 時再重新排程。
  • 時間片輪轉排程演算法 : 時間片輪轉排程是一種最古老,最簡單,最公平且使用最廣的演算法,又稱 RR(Round robin)排程。每個程式被分配一個時間段,稱作它的時間片,即該程式允許執行的時間。
  • 多級反饋佇列排程演算法 :前面介紹的幾種程式排程的演算法都有一定的侷限性。如短程式優先的排程演算法,僅照顧了短程式而忽略了長程式 。多級反饋佇列排程演算法既能使高優先順序的作業得到響應又能使短作業(程式)迅速完成。,因而它是目前被公認的一種較好的程式排程演算法,UNIX 作業系統採取的便是這種排程演算法。
  • 優先順序排程 : 為每個流程分配優先順序,首先執行具有最高優先順序的程式,依此類推。具有相同優先順序的程式以 FCFS 方式執行。可以根據記憶體要求,時間要求或任何其他資源要求來確定優先順序。

協程是一種使用者態的輕量級執行緒,又稱”微執行緒”,協程的排程完全由使用者控制。以下為兩者比較:

  1. 協程的執行效率非常高。因為子程式切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷
  2. 協程不需要多執行緒的鎖機制。在協程中控制共享資源不加鎖,只需要判斷狀態就好了。執行緒和程式是同步的,協程是非同步的!
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章