【操作系統】進程 & 線程相關知識點

1. 進程和線程

進程:資源分配的基本單位。
線程:獨立調度的基本單位。一個進程中可以有多個線程,共享進程資源。

進程和線程的區別:

  • 資源:進程是 資源分配 的基本單位,線程不擁有資源,線程可以訪問隸屬進程的資源。
  • 調度:線程是 獨立調度 的基本單位,同一進程中的線程切換不會引起進程切換,不同進程中的線程切換會引起進程切換。
  • 系統開銷:創建 / 撤銷 / 切換進程的開銷遠大於線程。由於創建或撤銷進程時,系統都要爲之分配或回收資源,如內存空間、I/O 設備等,所付出的開銷遠大於創建或撤銷線程時的開銷。類似地,在進行進程切換時,涉及當前執行進程 CPU 環境的保存及新調度進程 CPU 環境的設置,而線程切換時只需保存和設置少量寄存器內容,開銷很小。
  • 通信:進程間通信需要進程同步或互斥的輔助(進程間通信,InterProcessCommunication,IPC),線程間可直接讀 / 寫進程中的數據來通信。

進程控制塊(Process Control Block, PCB): 描述進程的基本信息和運行狀態,所謂的創建進程和撤銷進程,都是指對 PCB 的操作。

將進程分解爲線程可以有效地利用 多處理器多核計算機。例如,當我們使用 Microsoft Word 時,實際上是打開了多個線程。這些線程一個負責顯示,一個負責接收輸入,一個定時進行存盤…這些線程一起運轉,讓我們感覺到輸入和顯示同時發生,而不用鍵入一些字符等待一會兒才顯示到屏幕上。在不經意間,Word還能定期自動保存。

2. 進程狀態

3 個基本狀態:就緒,運行,阻塞。
在這裏插入圖片描述
5 個狀態:創建,就緒,運行,阻塞,終止

只有就緒態和運行態可以相互轉換,其它的都是單向轉換。就緒狀態的進程通過調度算法從而獲得 CPU 時間,轉爲運行狀態;而運行狀態的進程,在分配給它的 CPU 時間片用完之後就會轉爲就緒狀態,等待下一次調度。
在這裏插入圖片描述

  • 創建(create):申請一個空白的進程控制塊(PCB),並向 PCB 中填寫用於控制和管理進程的信息。
  • 就緒(ready):等待被調度。進程已獲得除 CPU 以外的所有資源,等待分配 CPU,一旦獲得CPU,便可立即執行。如果系統中有許多處於就緒狀態的進程,通常將它們按照一定的策略排成一個隊列,該隊列稱爲就緒隊列。【就緒態的進程屬於有執行資格,沒有執行權的進程】
  • 運行(running):佔用 CPU 資源運行,處於此狀態的進程數小於等於 CPU 數。對任何一個時刻而言,在單處理機的系統中,只有一個進程處於執行狀態而在多處理機系統中,有多個進程處於執行狀態。【運行態的進程屬於既有執行資格,又有執行權的進程】
  • 阻塞(waiting):正在執行的進程由於發生某事件(如 I/O 請求、申請緩衝區失敗等)暫時無法繼續執行,此時操作系統把處理機分配給了另外一個就緒的進程。阻塞狀態是缺少需要的資源從而由運行狀態轉換而來,但是該資源不包括 CPU 時間,缺少 CPU 時間會從運行態轉換爲就緒態。
  • 終止(terminated):當進程到達自然結束點,或出現了無法克服的錯誤,或被操作系統所終結,或被其他有終止權的進程所終結,將進入終止狀態。進入終止態的進程以後不能在再執行,但是操作系統中仍然保留了一個記錄,其中保存狀態碼和一些計時統計數據,供其他進程進行收集。一旦其他進程完成了對其信息的提取之後,操作系統將刪除其進程,即將其 PCB 清零,並將該空白的 PCB 返回給系統。

3. 進程調度算法

先來先服務 first-come first-serverd(FCFS)
非搶佔式的調度算法,按照請求的順序進行調度。有利於長作業,但不利於短作業,因爲短作業必須一直等待前面的長作業執行完畢才能執行,而長作業又需要執行很長時間,造成了短作業等待時間過長。

短作業優先 shortest job first(SJF)
非搶佔式的調度算法,按估計運行時間最短的順序進行調度。長作業有可能會餓死,處於一直等待短作業執行完畢的狀態。因爲如果一直有短作業到來,那麼長作業永遠得不到調度。

最短剩餘時間優先 shortest remaining time next(SRTN)
最短作業優先的搶佔式版本,按剩餘運行時間的順序進行調度。 當一個新的作業到達時,其整個運行時間與當前進程的剩餘時間作比較。如果新的進程需要的時間更少,則掛起當前進程,運行新的進程。否則新的進程等待。

時間片輪轉
將所有就緒進程按先來先服務(FCFS)原則排成一個隊列,每次調度時,把 CPU 時間分配給隊首進程,該進程可以執行一個時間片。當時間片用完時,由計時器發出時鐘中斷,調度程序便停止該進程的執行,並將它送往就緒隊列的末尾,同時繼續把 CPU 時間分配給隊首的進程。時間片輪轉算法的效率和時間片的大小有很大關係:因爲進程切換都要保存進程的信息並且載入新進程的信息,如果時間片太小,會導致進程切換得太頻繁,在進程切換上就會花過多時間;而如果時間片過長,那麼實時性就不能得到保證。

優先級調度
爲每個進程分配一個優先級,按優先級進行調度。爲了防止低優先級的進程永遠等不到調度,可以隨着時間的推移增加等待進程的優先級。

多級反饋隊列
一個進程需要執行 100 個時間片,如果採用時間片輪轉調度算法,那麼需要交換 100 次。多級隊列是爲這種需要連續執行多個時間片的進程考慮,它設置了多個隊列,每個隊列時間片大小都不同,例如 1,2,4,8,…。進程在第一個隊列沒執行完,就會被移到下一個隊列(也就是說分配給該進程的時間片會越來越大)。這種方式下,之前的進程只需要交換 7 次。每個隊列優先權也不同,最上面的優先權最高(時間片越小的隊列優先級越高)。因此只有上一個隊列沒有進程在排隊,才能調度當前隊列上的進程。可以將這種調度算法看成是時間片輪轉調度算法和優先級調度算法的結合。
在這裏插入圖片描述
從不同的需求來看,批處理系統 中沒有太多的用戶操作,在該系統中,調度算法目標是保證吞吐量和週轉時間(從提交到終止的時間),常用的進程調度算法是 先來先服務、短作業優先和最短剩餘時間優先交互式系統 有大量的用戶交互操作,在該系統中調度算法的目標是快速地進行響應。常用的進程調度算法是時間片輪轉、優先級調度和多級反饋隊列實時系統 要求一個請求在一個確定時間內得到響應,實時分爲硬實時和軟實時,前者必須滿足絕對的截止時間,後者可以容忍一定的超時。

4. 進程的同步與互斥

進程的同步和互斥是在 進程併發 下存在的概念,有了進程的併發,才產生了資源的競爭與協作,從而就要通過進程的同步、互斥、通信來解決資源的競爭與協作問題。

在多道程序設計系統中,同一時刻可能有許多進程,這些進程之間存在兩種基本關係:競爭關係協作關係。進程的同步、互斥、通信都是基於這兩種基本關係而存在的。

基本概念

互斥:爲了解決進程間 競爭關係(間接制約關係) 而引入進程互斥。當一個進程進入臨界區使用臨界資源時,另一個進程必須等待,當佔用臨界資源的進程退出臨界區後,另一進程才允許去訪問此臨界資源。

【比如 A、B 進程都需要使用打印機,如果進程 A 需要打印時,系統已將打印機分配給進程 B,則進程 A 必須阻塞。一旦進程 B 將打印機釋放,系統便將進程 A 喚醒,並將其由阻塞狀態變爲就緒狀態】

同步:爲了解決進程間 鬆散的協作關係(直接制約關係) 引入進程同步。爲了完成某個目而建立的兩個或多個進程,這些進程需要在某些位置協調工作次序,信息傳遞而產生的制約關係,直接制約關係是由於進程間的相互合作而引起的。

【比如 A、B 進程,A 產生數據,B 計算結果,AB 公用一個緩存區。緩存區爲空時,B 不能運行,等待 A 向緩存區傳遞數據後 B 才能運行,緩存區滿時,A 不能運行,等待 B 取走數據後,A 才能運行。此時 AB 爲直接制約關係】

通信:爲了解決進程間 緊密的協作關係 而引入進程通信。

臨界資源:一次僅允許一個進程使用的資源稱爲臨界資源。

臨界區(Critical Section):對臨界資源進行訪問的那段代碼稱爲臨界區,每次只允許一個進程進入臨界區。多個進程涉及到同一個臨界資源的的臨界區稱爲 相關臨界區,臨界區是一種輕量級的同步機制,與互斥和事件這些內核同步對象相比,臨界區是 用戶態 下的對象,即只能在同一進程中實現線程互斥。因無需在用戶態和核心態之間切換,所以工作效率比較互斥來說要高很多。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。

將進入並訪問臨界資源的代碼分爲 4 個部分:

  • 進入區。爲了進入臨界區使用臨界資源,在進入區要檢查可否進入臨界區,如果可以進入臨界區,則應設置正在訪問臨界區的標誌,以阻止其他進程同時進入臨界區。
  • 臨界區。進程中訪問臨界資源的那段代碼,又稱臨界段。
  • 退出區。將正在訪問臨界區的標誌清除。
  • 剩餘區。代碼中的其餘部分。
    do {  
    entry section; //進入區  
    critical section; //臨界區  
    exit section; //退出區  
    remainder section; //剩餘區  
    } while (true)  

【競爭關係】

多個進程間 彼此無關不受對方的影響,但是它們共用了一套資源,從而出現多個進程競爭資源的問題。

資源競爭引發的問題:

  • 死鎖:一組進程如果都獲得了部分資源,還想要得到其他進程所佔有的資源,最終所有的進程將陷入死鎖。
  • 飢餓:一個進程由於其他進程總是優先於它而被無限期拖延。

解決資源競爭:通過進程互斥,使若干個進程要使用同一共享資源時,保證任何時刻最多允許一個進程使用,其他要使用該資源的進程必須等待,直到佔有資源的進程釋放該資源。

【協作關係】

多個進程共同完成某項任務,但每個進程都獨立地以不可預知的速度推進,故需要在某些協調點上協調每個進程的工作。比如當某一進程到達協調點,但尚未獲得其他進程發來的消息,此時應主動阻塞自己,直到獲得必要的消息後再次喚醒繼續執行。

協作關係可分爲:1)並不知道對方是誰的 “鬆散式協作”(同步),比如共享一個緩衝區協作;也可以是 2)知道對方名字的 “緊密式協作”(通信)。

協作的優點:有利於共享信息、有利於加快計算速度、有利於實現模塊化程序設計。

解決進程協作:通過進程同步,可以協調多個進程的活動。進程互斥屬於特殊的進程同步,即對資源使用次序上的一種協調。

進程通信:進程之間互相交換信息的工作稱爲進程通信 IPC (InterProcess Communication)(主要是指大量數據的交換)

【同步機制遵循的規則】

  • 空閒讓進:當無進程處於臨界區時,表明臨界資源處於空閒狀態,應允許一個請求進入臨界區的進程立即進入自己的臨界區,以有效利用臨界資源。
  • 忙則等待:當已有進程進入臨界區時,表明臨界資源正在被訪問,因而其他試圖進入臨界區的進程都必須等待,以保證對臨界資源的互斥訪問。
  • 有限等待:對要求訪問的臨界資源的進程,應保證在有限時間內能進入自己的臨界區,以免陷入“死等” 狀態。
  • 讓權等待:當進程不能進入自己的臨界區時,應立即釋放處理機,以免陷入"忙等" 狀態。

多線程同步機制

  1. 臨界區:通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。 用於實現“排他性佔有”,範圍是單一進程,屬於局部對象,非核心對象,可以實現線程間互斥,不能來實現同步。
  2. 互斥量:爲協調共同對一個共享資源的單獨訪問而設計的。屬於核心對象,可以在不同進程的不同線程之間實現“排他性佔有”,可以有名稱,因此可以被其他進程開啓,但只能被擁有它的線程釋放。
  3. 信號量:爲控制一個具有有限數量用戶資源而設計。 屬於核心對象,可以有名稱,因此可以被其他進程開啓,可以被任何一個線程釋放,既能實現線程間互斥也能實現線程間同步。
  4. 事件:用來通知線程有一些事件已發生,從而啓動後繼任務的開始。屬於核心對象,可以用於實現線程的同步與互斥,可以有名稱,因此可以被其他進程開啓。

臨界區和互斥量 都有“線程所有權”的概念,所以它們 不能用來實現線程間的同步的,只能用來實現互斥。而 事件和信號量都可以實現線程和進程間的互斥和同步

就使用效率來說,臨界區的效率是最高的,因爲它不是內核對象,而其它的三個都是核心對象,要藉助操作系統來實現,效率相對來說就比較低。

ps:進程的同步方式是線程的同步方式的子集。

1. 臨界區 Critical Section

在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區,那麼 在有一個線程進入後其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。

臨界區包含兩個操作原語:

  • EnterCriticalSection():進入臨界區
  • LeaveCriticalSection():離開臨界區

Enter 語句執行後必須確保與之匹配的 Leave 能夠執行,否則臨界區的共享資源永遠不會被釋放。

特點:臨界區同步速度很快(處於用戶態),但只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。

2. 互斥量 Mutex

互斥量跟臨界區很相似,只有擁有互斥對象的線程才具有訪問資源的權限,由於互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其他線程在獲得後得以訪問資源。

互斥量包含的操作原語:

  • CreateMutex():創建一個互斥量
  • OpenMutex():打開一個互斥量
  • ReleaseMutex():釋放互斥量
  • WaitForMultipleObjects():等待互斥量對象

特點:互斥量與臨界區的作用非常相似,但互斥量比臨界區複雜。因爲互斥量是可以命名的,也就是說它可以跨越進程使用。使用互斥不僅僅 能夠在 同一應用程序不同線程 中實現資源的安全共享,而且可以在 不同應用程序的線程 之間實現對資源的安全共享。所以創建互斥量需要的資源更多。如果只爲了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量 。

臨界區和互斥量的區別

  1. 臨界區只能用於對象在 同一進程裏線程間的互斥訪問;互斥量可以用於對象進程間或線程間的互斥訪問。
  2. 臨界區是 非內核對象,只在 用戶態 進行鎖操作,速度快;互斥量是 內核對象,在 核心態 進行鎖操作,速度慢。
  3. 臨界區和互斥量在 Windows 平臺都下可用;Linux下只有互斥量可用。
  4. 臨界區通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問;互斥量是爲協調共同對一個共享資源的單獨訪問而設計的。

互斥量和信號量的聯繫

mutex互斥量是semaphore信號量的一種特殊情況(n=1時)。也就是說,完全可以用信號量替代互斥量。

3. 信號量 Semaphores

信號允許多個線程同時使用共享資源,它指出了同時訪問共享資源的線程最大數目。它 允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。

信號量 S 是一個整型變量,S 大於等於 0 時代表可供併發進程使用的資源實體數,S 小於 0 時則表示正在等待使用共享資源的進程數。信號量 S 支持加法操作 up 和減法操作 down,即 PV 操作

信號量包含的操作原語:

  • CreateSemaphore():創建一個信號量
  • OpenSemaphore():打開一個信號量
  • ReleaseSemaphore():釋放信號量
  • WaitForSingleObject():等待信號量

用 CreateSemaphore() 創建信號量時要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置爲最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減 1,只要當前可用資源計數是大於 0 的,就可以發出信號量信號。但是當前可用計數減小到 0 時則說明當前佔用資源的線程數已經達到了所允許的最大數目,不再允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源後,應在離開的同時通過 ReleaseSemaphore() 將當前可用資源計數加 1。在任何時候當前可用資源計數決不可能大於最大資源計數。

4. 事件 Event

事件對象也可以通過通知操作的方式來保持線程的同步。並且可以實現不同進程中的線程同步操作。

事件包含的操作原語:

  • CreateEvent():創建一個事件
  • OpenEvent():打開一個事件
  • SetEvent():回置事件
  • WaitForSingleObject():等待一個事件
  • WaitForMultipleObjects():等待多個事件

5. 管程 Monitor

管程即監視器,它監視的是進程或線程的同步操作。具體來說,管程就是 一組子程序、變量和數據結構的組合。言下之意,把需要同步的代碼用一個管程的構造框起來,即將需要保護的代碼置於 begin monitor 和 end monitor 之間,即可獲得同步保護,也就是 任何時候只能有一個線程活躍在管程裏面。在一個時刻只能有一個進程使用管程。進程在無法繼續執行的時候不能一直佔用管程,否則其它進程永遠不能使用管程。

使用信號量機制實現的生產者消費者問題需要客戶端代碼做很多控制,而管程把控制的代碼獨立出來,不僅不容易出錯,也使得客戶端代碼調用更容易。【C 語言不支持管程】

管程引入了條件變量以及相關的操作:wait()signal() 來實現同步操作。對條件變量執行 wait() 操作會導致調用進程阻塞,把管程讓出來給另一個進程持有。signal() 操作用於喚醒被阻塞的進程。

同步操作的保證是由編譯器來執行的,編譯器在看到begin monitor和end monitor時就知道其中的代碼需要同步保護,在翻譯成低級代碼時就會將需要的操作系統原語加上,使得兩個線程不能同時活躍在同一個管程內。

在管程中使用兩種同步機制:鎖用來進行互斥,條件變量用來控制執行順序。從某種意義上來說,管程就是鎖+條件變量。

條件變量就是線程可以在上面等待的東西,而另外一個線程則可以通過發送信號將在條件變量上的線程叫醒。因此,條件變量有點像信號量,但又不是信號量,因爲不能對其進行up和down操作。

管程最大的問題就是對編譯器的依賴,因爲我們需要將編譯器需要的同步原語加在管程的開始和結尾。此外,管程只能在單臺計算機上發揮作用,如果想在多計算機環境下進行同步,那就需要其他機制了,而這種其他機制就是消息傳遞。

6. 消息傳遞

消息傳遞是通過同步雙方經過互相收發消息來實現,它有兩個基本操作:發送send和接收receive。他們均是操作系統的系統調用,而且既可以是阻塞調用,也可以是非阻塞調用。而同步需要的是阻塞調用,即如果一個線程執行receive操作,就必須等待受到消息後才能返回。也就是說,如果調用receive,則該線程將掛起,在收到消息後,才能轉入就緒。

消息傳遞最大的問題就是消息丟失和身份識別。由於網絡的不可靠性,消息在網絡間傳輸時丟失的可能性較大。而身份識別是指如何確定收到的消息就是從目標源發出的。其中,消息丟失可以通過使用TCP協議減少丟失,但也不是100%可靠。身份識別問題則可以使用諸如數字簽名和加密技術來彌補。

7. 柵欄

柵欄顧名思義就是一個障礙,到達柵欄的線程必須停止下來,知道出去柵欄後才能往前推進。該院與主要用來對一組線程進行協調,因爲有時候一組線程協同完成一個問題,所以需要所有線程都到同一個地方匯合之後一起再向前推進。

例如,在並行計算時就會遇到這種需求,如下圖所示:
在這裏插入圖片描述

5. 實現同步互斥的基本方法

5.1 軟件同步機制

在進入區設置和檢查一些標誌來標明是否有進程在臨界區,如果已有進程在臨界區,則在進入區通過循環檢查進行等待,進程離開臨界區後則在退出區修改標誌。

算法一:單標誌法
設置一個公用整型變量 turn,用於指示被允許進入臨界區的進程編號,比如 turn = 0 ,則允許 P0 進程進入臨界區。該算法可確保每次只允許一個進程進入臨界區。
兩個進程必須交替進入臨界區,如果某個進程不進入臨界區,另一個進程也不進入臨界區,會造成資源利用的不充分(違背 “空閒讓進”)。

算法二:雙標誌法先檢查
該算法的基本思想是在每一個進程訪問臨界區資源之前,先查看一下臨界資源是否正在被訪問,若被訪問則該進程需等待,否則進程進入自己的臨界區。爲此,設置了一個數據 flag[i],如果 flag[i] 值爲 FLASE,則表示 i 進程未進臨界區,值爲 TRUE,表示 i 進程已進入臨界區。
優點是不要交替進入,可連續使用;缺點是 pi 和 pj 可能同時進入臨界區(違背 ”忙則等待“)。即在檢查對方 flag 之後和切換自己的 flag 之間有一段時間,結果都檢查通過。這裏問題出在檢查和修改操作不能一次進行。

算法三:雙標誌法後檢查
算法二先檢測進程狀態標誌後,再置自己標誌,由於在檢測和放置中可插入另一個進程到達時的檢測操作,會造成兩個進程分別檢測後。同時進入臨界區。爲此,算法三採用先設置自己標誌爲TRUE,再檢測對方狀態標誌,若對方標誌爲TURE,則進程等待,否則進入臨界區。
當兩個進程幾乎同時想要進入臨界區時,他們分別將自己的標誌值 flag 設置爲 TRUE,並且同時檢測對方的狀態(執行while語句),發現對方也要進入臨界區,預算對方互相謙讓了,結果誰也進不了臨界區,從而導致"飢餓"現象。

算法四:Peterson算法
爲了防止兩個進程爲進入臨界區而無限等待,又設置變量 turn,指示不允許進入臨界區的進程編號,每個進程在設置自己的標誌後再設置 turn 標誌,不允許另一個進程進入。這時,在同時檢測另一個進程狀態標誌和不允許進入標誌,這樣可以保證兩個進程同時要求進入臨界區,只允許一個進程進入臨界區。
本算法的基本思想是算法一和算法三的結合。利用 flag 解決臨界資源的互斥訪問,而利用 turn 解決“飢餓”現象。該算法滿足解決臨界區問題的 3 個標準:互斥訪問、進入、有限等待。

5.2 硬件同步機制

1. 關中斷

通過硬件實現臨界區最簡單的辦法就是關CPU的中斷。從計算機原理我們知道,CPU進行進程切換是需要通過中斷來進行。如果屏蔽了中斷那麼就可以保證當前進程順利的將臨界區代碼執行完,從而實現了互斥。這個辦法的步驟就是:屏蔽中斷–執行臨界區–開中斷。但這樣做並不好,這大大限制了處理器交替執行任務的能力。並且將關中斷的權限交給用戶代碼,那麼如果用戶代碼屏蔽了中斷後不再開,那系統豈不是跪了?

2. 硬件指令方式

  • 利用 Test-and-Set 指令實現互斥
  • 利用 Swap 指令實現互斥

5.3 信號量實現

信號量(Semaphores):信號允許多個線程同時使用共享資源,它指出了同時訪問共享資源的線程最大數目。它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。

信號量 S 是一個整型變量,S 大於等於零時代表可供併發進程使用的資源實體數,但 S 小於零時則表示正在等待使用共享資源的進程數。 可以對信號量 S 執行加減操作,即 PV 操作。

信號量包含的操作原語:

  • CreateSemaphore():創建一個信號量
  • OpenSemaphore():打開一個信號量
  • ReleaseSemaphore():釋放信號量
  • WaitForSingleObject():等待信號量

用 CreateSemaphore()創建信號量時要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置爲最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數 就會減1,只要當前可用資源計數是大於0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前佔用資源的線程數已經達到了所允許的最大數目, 不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源後,應在離開的同時通過 ReleaseSemaphore()函數將當前可 用資源計數加 1。在任何時候當前可用資源計數決不可能大於最大資源計數。

信號量操作: P和V操作分別來自荷蘭語Passeren和Vrijgeven,分別表示佔有和釋放。P V操作是操作系統的原語,意味着具有原子性。

P 操作首先減少信號量,表示有一個進程將佔用或等待資源,然後檢測S是否小於 0,如果小於 0 則阻塞,如果大於 0 則佔有資源進行執行。V 操作是和 P 操作相反的操作,首先增加信號量,表示佔用或等待資源的進程減少了 1 個。然後檢測 S 是否小於 0,如果小於 0 則喚醒等待使用 S 資源的其它進程。

  • 如果信號量 S 大於 0 ,執行 -1 操作;
  • 如果信號量 S 等於 0,進程睡眠,等待信號量大於 0;
  • 對信號量 S 執行 +1 操作,喚醒睡眠的進程讓其繼續執行。

如果信號量的取值只能爲 0 或者 1,那麼就成爲了 互斥量(Mutex) ,0 表示臨界區已經加鎖,1 表示臨界區解鎖。

6. PV操作

與信號量對應的就是 PV 操作:

P 操作:申請資源

  • 信號量 S 減 1;
  • 若 S 減 1 後仍大於等於零,則進程繼續執行;
  • 若 S 減 1 後小於零,則該進程被阻塞後進入與該信號相對應的隊列中,然後轉入進程調度。

V 操作:釋放資源

  • 信號量 S 加 1 ;
  • 若 S 加 1 後結果大於零,則進程繼續執行;
  • 若 S 加 1 後結果小於等於零,則從該信號的等待隊列中喚醒一個等待進程,然後再返回原進程繼續執行或轉入進程調度。

7. 同步經典問題

7.1 生產者和消費者問題

問題描述:生產者-消費者問題是一個經典的進程同步問題,該問題最早由Dijkstra提出,用以演示他提出的信號量機制。本作業要求設計在同一個進程地址空間內執行的兩個線程。生產者線程生產物品,然後將物品放置在一個空緩衝區中供消費者線程消費。消費者線程從緩衝區中獲得物品,然後釋放緩衝區。當生產者線程生產物品時,如果沒有空緩衝區可用,那麼生產者線程必須等待消費者線程釋放出一個空緩衝區。當消費者線程消費物品時,如果沒有滿的緩衝區,那麼消費者線程將被阻塞,直到新的物品被生產出來

這裏生產者和消費者是既同步又互斥的關係,首先只有生產者生產了,消費着才能消費,這裏是同步的關係。但他們對於臨界區的訪問又是互斥的關係。因此需要三個信號量empty和full用於同步緩衝區,而mut變量用於在訪問緩衝區時是互斥的。

7.2 讀者和寫者問題

問題描述:一個數據文件或記錄,統稱數據對象,可被多個進程共享,其中有些進程只要求讀稱爲"讀者",而另一些進程要求寫或修改稱爲"寫者"。

規定:允許多個讀者同時讀一個共享對象,但禁止讀者、寫者同時訪問一個共享對象,也禁止多個寫者訪問一個共享對象,否則將違反Bernstein併發執行條件。

通過描述可以分析,這裏的讀者和寫者是互斥的,而寫者和寫者也是互斥的,但讀者之間並不互斥。由此我們可以設置3個變量,一個用來統計讀者的數量,另外兩個分別用於對讀者數量讀寫的互斥,讀者和讀者寫者和寫者的互斥。

7.3 哲學家進餐問題

問題描述:有五個哲學家,他們的生活方式是交替地進行思考和進餐。哲學家們公用一張圓桌,周圍放有五把椅子,每人坐一把。在圓桌上有五個碗和五根筷子,當一個哲學家思考時,他不與其他人交談,飢餓時便試圖取用其左、右最靠近他的筷子,但他可能一根都拿不到。只有在他拿到兩根筷子時,方能進餐,進餐完後,放下筷子又繼續思考。

根據問題描述,五個哲學家分別可以看作是五個進程。五隻筷子分別看作是五個資源。只有當哲學家分別擁有左右的資源時,才得以進餐。如果不指定規則,當每個哲學家手中只拿了一隻筷子時會造成死鎖,從而五個哲學家都因爲喫不到飯而餓死。因此我們的策略是讓哲學家同時拿起兩隻筷子。因此我們需要對每個資源設置一個信號量,此外,還需要使得哲學家同時拿起兩隻筷子而設置一個互斥信號量。

8. 進程間通信

進程通信和進程同步的區別:

  • 進程同步:控制多個進程按一定順序執行
  • 進程通信:進程間傳輸信息

進程通信是一種手段,而進程同步是一種目的。也可以說,爲了能夠達到進程同步的目的,需要讓進程進行通信,傳輸一些進程同步所需要的信息。

進程通信和線程通信的區別:

  • 線程通信一般是指 同一進程內的線程 進行通訊,由於在同一進程內共享地址空間,因此交互比較容易,全局變量之類的都能起到作用。
  • 進程通信一般是指 不同進程間的線程 進行通訊,由於地址空間不同,因此需要使用操作系統相關機制進行“中轉”,比如共享文件、管道、SOCKET。

進程通信分類:

根據交換信息量的多少和效率的高低,進程通信分爲低級通信和高級通信。

  • 低級通信:只能傳遞狀態和整數值(控制信息)。特點是傳送信息量小,效率低,每次通信傳遞的信息量固定,若傳遞較多信息則需要進行多次通信。編程比較複雜,由用戶直接實現通信的細節,容易出錯。
  • 高級通信:提高信號通信的效率,傳遞大量數據,減輕程序編制的複雜度。有三種高級通信方式:1)共享內存模式,2)消息傳遞模式,3)共享文件模式。

進程間通信方式:

  • 共享內存
  • 消息傳遞
  • 文件映射
  • 管道
  • 消息隊列
  • 網絡(套接字)

8.1 共享內存

共享內存(Shared-memory):相互通訊的進程有共享存儲區,進程間可以通過直接讀寫共享存儲區的變量來交互數據。共享內存屬於間接通信。

共享內存是最快捷的 IPC(直接對內存存取),通常與其他機制結合使用,比如共享內存區的同步、互斥需要使用信號量來實現。另外,數據的發送方不關心數據由誰接收,數據的接收方也不關心數據是由誰發送的,所以存在安全隱患。

8.2 消息傳遞

消息傳遞(message-passing):通過操作系統的相應系統調用進行消息傳遞通訊,分爲直接通信和間接通信兩種方式。

  • 直接通信方式:點到點的發送。基本思想是進程在發送和接收消息時直接指明接收者或發送者進程 ID。缺點是必須指定接收進程 ID。
  • 間接通信方式:以信箱爲媒介進行傳遞,可以廣播。基本思想是系統爲每個信箱設一個消息隊列,消息發送和接收都指向該消息隊列。優點是很容易建立雙向通訊鏈(只要對信箱說明爲讀寫打開)。缺點是必須有一個通訊雙方共享的一個邏輯消息隊列。

8.3 文件映射

文件映射(Memory-Mapped Files):能使進程把文件內容當作進程地址區間的一塊內存那樣來對待。因此,進程不必使用文件 I/O 操作,只需簡單的指針操作就可讀取和修改文件的內容。

文件映射是在多個進程間共享數據的非常有效方法,有較好的安全性。但文件映射只能用於本地機器的進程之間,不能用於網絡中,而開發者還必須控制進程間的同步。

8.4 管道

管道通信(Pipe):管道是一種信息流緩衝機構,以先進先出(FIFO)方式組織數據傳輸。管道可分爲:

  • 匿名管道(Anonymous Pipe):只適用於父子進程之間通信。管道能夠把信息從一個進程的地址空間拷貝到另一個進程的地址空間。
  • 命名管道(Named Pipe / FIFO):命名管道有自己的名字和訪問權限的限制,就像一個文件一樣(存在於文件系統),可以用於不相關進程間的通信。進程通過使用管道的名字獲得管道。常用於客戶-服務器應用程序中作爲匯聚點,在客戶進程和服務器進程之間傳遞數據。

特點:1)管道是一個單向通信信道(半雙工通信),具有固定的讀端和寫端。如果進程間要進行雙向通信,通常需要定義兩個管道。2)調用 pipe 函數創建管道,管道可以看作是一種特殊的文件,可調用 read(), write() 函數對其進行讀寫操作。但又不屬於任何文件系統,並只存在於內存中。3)匿名管道只能用於具有親緣關係的進程之間的通信(父子進程或者兄弟進程間)。

8.5 消息隊列

消息隊列:消息隊列是消息的鏈接表,存放在內核中。一個消息隊列由一個標識符(隊列 ID)進行標識。

特點:1)消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級。2)消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。3)消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。

消息隊列相比命名管道,有以下優點:

  • 消息隊列可以獨立於讀寫進程存在,從而避免了 FIFO 中同步管道的打開和關閉時可能產生的困難;
  • 避免了 FIFO 的同步阻塞問題,不需要進程自己提供同步方法;
  • 讀進程可以根據消息類型有選擇地接收消息,而不像 FIFO 那樣只能默認地接收。

8.6 套接字

套接字(Socket):套接字可以看成是兩個網絡應用程序進行通信時,各自通信連接中的端點,這是一個 邏輯上的概念,即網絡環境中進程間通信的 API(應用程序編程接口)。與其它通信機制不同的是,套接字可用於不同主機間的進程通信。
在這裏插入圖片描述
Socket 意思是 “插頭”,實際上服務器就像一個大插排,上面有很多插頭,客戶端就像一個一個的插頭,每個線程表示一條電源線,客戶端將電線插頭插到服務器插排的插座上,就可以開始通信了。

表示方法:Socket =(IP地址:端口號)

每一個傳輸層連接唯一地被通信兩端的兩個端點(即兩個套接字)所確定。例如:如果 IP 地址是 210.37.145.1,而端口號是 23,那麼得到套接字就是(210.37.145.1:23)

套接字類型

  • 流套接字(SOCK_STREAM):流套接字用於提供面向連接、可靠的數據傳輸服務。該服務將保證數據能夠實現無差錯、無重複送,並按順序接收。流套接字之所以能夠實現可靠的數據服務,原因在於其使用了傳輸控制協議,即TCP(The Transmission Control Protocol)協議。
  • 數據報套接字(SOCK_DGRAM):數據報套接字提供一種無連接的服務。該服務並不能保證數據傳輸的可靠性,數據有可能在傳輸過程中丟失或出現數據重複,且無法保證順序地接收到數據。數據報套接字使用UDP( User DatagramProtocol)協議進行數據的傳輸。由於數據報套接字不能保證數據傳輸的可靠性,對於有可能出現的數據丟失情況,需要在程序中做相應的處理。
  • 原始套接字(SOCK_RAW):原始套接字與標準套接字(標準套接字指流套接字和數據報套接字)的區別在於:原始套接字可以讀寫內核沒有處理的IP數據包,而流套接字只能讀取 TCP 協議的數據,數據報套接字只能讀取 UDP 協議的數據。因此,如果要訪問其他協議發送的數據必須使用原始套接。

工作流程:要通過互聯網進行通信,至少需要一對套接字,其中一個運行於客戶端(Client Socket),另一個運行於服務器端(Server Socket)。根據連接啓動的方式以及本地套接字要連接的目標,套接字之間的連接過程可以分爲三個步驟:

  1. 服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態。
  2. 客戶端請求:客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然後就向服務器端接字提出連接請求。
  3. 連接確認:當服務器端套接字監聽到客戶端套接字的連接請求,就會響應客戶端套接字的請求,建立一個新的線程,並把服務器端套接字的描述發送給客戶端。一旦客戶端確認了此描述,連接就建立好了。而服務器端套接字繼續處於監聽狀態,接收其他客戶端套接字的連接請求。

套接字調用分類:根據套接字的不同類型,可以將套接字調用分爲面向連接服務和無連接服務。

  • 面向連接服務的主要特點:1)數據傳輸過程必須經過建立連接、維護連接和釋放連接3個階段;2)在傳輸過程中,各分組不需要攜帶目的主機的地址;3)可靠性好,但由於協議複雜,通信效率不高。
  • 面向無連接服務的主要特點:1)不需要連接的各個階段;2)每個分組都攜帶完整的目的主機地址,在系統中獨立傳送;3)由於沒有順序控制,所以接收方的分組可能出現亂序、重複和丟失現象;4)通信效率高,但可靠性不能確保。

參考資料:
操作系統4————進程同步
進程的同步、互斥、通信的區別,進程與線程同步的區別
Peterson算法的簡單分析
CS-Notes 進程與線程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章