1. 程式人生 > 其它 >《Go併發程式設計實戰》系列一:多程序程式設計

《Go併發程式設計實戰》系列一:多程序程式設計

目錄

併發與並行

  併發程式是指可以被同事發起執行的程式,並行程式可以在並行的硬體上執行的併發程式,這兩者稍有不同,併發程式代表了所有可以實現併發行為的程式,其中包含了並行程式。
  序列程式所有程式碼的先後順序都是確定的,併發程式中只有部分程式碼有序,其中有一些程式碼的執行順序無明確指定,這被稱為不確定性。
  併發程式內部會被劃分為多個部分,每個部分都是一個序列程式,這些序列程式中又會存在互動需求,我們需要協調他們的執行,就涉及到同步。

同步

  同步的作用是避免在併發訪問共享資源時可能發生的衝突,以及確保有條不紊的傳遞資料。如果程式想使用一個共享資源,就必須請求該資源並獲取到對它的訪問權,如果不需要時,應該放棄對該資源的訪問權。即同一時刻某個資源應該只被一個程式所佔用。

非同步

  傳遞資料是併發程式內部的一種互動方式,也成為併發程式內部的通訊,協調這種內部通訊的方式不只“同步”一種,我們也可使用非同步的方式進行。這種方式可使資料不加延遲的傳送給資料接收方。資料將會被臨時存放在一個稱為資料通訊快取的資料結構中。通訊快取是一種特殊的共享資源,他可同時被多個程式使用,資料結合搜房可以在準備就緒後按照資料的寫入順序進行接收。

多程序程式設計

程序間通訊的方式用來支援多個程序間協作完成任務,這種通訊也被稱為IPC(Inter-Process Communication),Linux中支援的IPC有多種,可分為三類:
 

  1. 基於通訊的IPC方法

    其中又分為以資料傳送為手段的IPC方法和以共享記憶體為手段的IPC方法,前者包括管道(PIPE)和訊息佇列(message queue),管道可以用來傳送位元組流,訊息佇列可以用來傳送結構化的訊息物件。以共享記憶體為手段的IPC方法主要以共享記憶體區(shared memory)為代表,它是最快的一種IPC方法。

  2. 基於訊號的IPC方式
    即作業系統的訊號(signal)機制,它是唯一的一種非同步IPC的方法。

  3. 基於同步的IPC方式,
    即訊號量(semaphore)

Go語言中支援的IPC方法有管道、訊號和socket


程序的定義

一個程式的執行叫做程序,程序也是作業系統進行資源分配的一個基本單位。

程序可使用(fork)建立若干個新的程序,前者稱為後者的父程序,後者稱為前者的子程序。每個子程序都源自父程序的一個副本,他會獲得父程序的資料段、堆和棧的副本,並與父程序共享程式碼段。每一份副本都是獨立的。子程序對副本的修改對其父程序和兄弟程序都是不可見的,反之亦然。Linux 作業系統核心使用寫時複製(Copy on Write)等技術來提高程序建立的效率。

Unix/Linux中每一個程序都有父程序,所有的程序共同組成了一個樹狀結構。核心啟動程序作為程序樹的根,負責系統的初始化操作。

為了管理程序,核心必須對每個程序的屬性和行為進行詳細記錄,包括程序的優先順序、狀態、虛擬地址範圍以及各種訪問許可權等等,這些資訊都會被記錄在每個程序的程序描述符中。程序描述符是一個複雜的資料結構。儲存在程序描述符中的程序ID(也被稱為PID)是程序在作業系統的唯一標識。其中程序ID為1的程序就是核心啟動程序。程序ID為一個非負整數。此外,程序描述符中海油當前程序的父程序的ID也被稱為(PPID)。

我們可通過Go標準庫程式碼檢視當前程序的PID和PPID:

pid := os.Getpid()
ppid := os.Getppid()

程序的狀態

Linux中每個程序在每個時刻都有狀態,可能的狀態有6個:可執行狀態、可中斷的睡眠狀態、不可中斷的睡眠狀態、暫停狀態或跟蹤狀態、殭屍狀態和退出狀態,具體每個狀態的輪轉可見參考文章

程序的空間

使用者程序(程式的執行例項)總會生存在使用者空間,無法與其所在計算機的硬體進行互動。核心可與硬體互動,但它生存在核心空間。使用者程序無法直接訪問核心空間。使用者空間和核心空間瓜分了作業系統可支配的記憶體區域。

使用者空間範圍為0~TASK_SIZE,核心空間則佔據了剩下的空間。TASK_SIZE由所在計算機體系結構確定,是一個常數,如圖:

記憶體區域中的每一個單元都是有地址的,地址由指標來標識和定位。通過指標來尋找記憶體單元的操作也稱為記憶體定址。指標是一個正整數,由若干個二進位制位來表示,具體的二進位制位的數量由計算機(CPU)的字長來決定。如在32位計算機中可以有效標識2的32次方個記憶體單元,64位計算機中可以有效標識2的64次方個記憶體單元。

此處所說的地址並非實體地址,而是虛擬地址,而由虛擬地址來標識的記憶體區域又稱為虛擬地址空間,也被稱為虛擬記憶體。虛擬記憶體的最大容量與實際可用的實體記憶體的大小無關。CPU和核心會負責維護虛擬記憶體與實體記憶體之間的關係。另外,核心會為每個使用者程序分配的是虛擬記憶體而不是實體記憶體。每個使用者程序分配到的虛擬記憶體總是在使用者空間中,而核心空間為核心專用。

核心會將程序的虛擬記憶體劃分為若干頁(page),而實體記憶體單元的劃分由CPU負責。一個實體記憶體單元被稱為一個葉框(page frame)。不同程序的大多數頁都會與不同的葉框對應。

系統呼叫

使用者程序使用使用者空間和核心空間之間橋樑(允許使用者程序使用核心功能的介面)的行為稱為系統呼叫,與普通函式相比,系統呼叫是向核心空間發出的一個明確請求,普通函式只定義瞭如何獲取一個給定的服務。系統呼叫會導致核心空間中資料的存取和指令的執行,而普通函式卻只能在使用者空間進行。此外,系統呼叫是核心的一部分。

核心態與使用者態

為了確保系統安全,核心依據由CPU提供的、可以讓進城駐留的特權級別建立了兩個特權狀態——核心態與使用者態。大部分情況下,CPu都處於使用者態,這時CPU只能對使用者空間進行訪問。換而言之,CPU在使用者態下執行的使用者程序是不能與核心接觸的。

當用戶程序發出一個系統呼叫時,核心會把CPU從核心態切換到使用者態,而後讓CPU執行對應的核心函式。這就相當於使用者程序可以通過系統呼叫使用核心提供的功能,當核心函式執行完畢後,核心會把CPU從核心態切回用戶態,並把執行結果返回給使用者程序。

程序切換和排程

Linux作業系統可憑藉CPU的威力快速地在多個程序之間進行切換,這被稱為程序間的上下文切換,在程序換入換出期間必須要做的任務統稱為程序切換。
為了使各個生存著的程序都有執行的機會,核心還要考慮下次切換時執行哪個程序、何時切換、換下的程序何時換上等等。解決類似問題的方案和任務統稱為程序排程。常見的程序排程方法有:

1. 先來先去服務

  2. 時間片輪轉法

  3. 多級反饋佇列演算法

  4. 最短程序優先

  5. 最短剩餘時間優先

  6. 最高響應比優先

  7. 多級反饋佇列排程演算法