操作系統基礎筆記(第二部分)

操作系統基礎筆記(第二部分)

1. 進程的定義組成組織方式特徵

在這裏插入圖片描述

1. 進程的定義

  • 程序
    • 就是一個指令序列
    • 早期的計算機(只支持單道程序)
  • 引入多道程序技術之後:
    • 爲了方便操作系統管理,完成各程序併發執行,引入了進程進程實體概念
    • PCB,程序段,數據段三部分構成了進程實體(進程映像)。一般情況下,我們把進程實體簡稱爲進程
      • 系統爲每個運行的程序配置一個數據結構,稱爲進程控制塊(PCB),用來描述進程的各種信息(如程序代碼存放位置)
      • 所謂創建進程,實質上是創建進程實體中的PCB;而撤銷進程,實質上是撤銷進程實體中的PCB
      • 注意:PCB是進程存在的唯一標誌
  • 從不同的角度,進程可以有不同的定義,比較傳統典型的定義有:
    • 進程是程序的一次執行過程
    • 進程是一個程序及其數據在處理機上順序執行時所發生的活動
    • 進程是具有獨立功能的程序在數據集合上運行的過程強調“動態性”),它是系統進行資源分配和調度的一個獨立單位
  • 引入進程實體的概念後,可把進程定義爲:
    • 進程是進程實體的運行過程,是系統進行資源分配調度的一個獨立單位
  • 注意
    • 嚴格來說,進程實體和進程並不一樣,進程實體是靜態的,進程則是動態

2. 進程的組成

  • 進程(進程實體)由程序段,數據段,PCB三部分組成
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述

3. 進程的組織

  • 注意
    • 進程的組成討論的是一個進程內部由哪些部分構成的問題,而進程的組織討論的是多個進程之間的組織方式問題
      在這裏插入圖片描述

1. 鏈接方式

在這裏插入圖片描述

2. 索引方式

在這裏插入圖片描述

4. 進程的特徵

在這裏插入圖片描述

知識回顧

在這裏插入圖片描述

2. 進程的狀態與轉換

在這裏插入圖片描述

1. 進程的狀態–三種基本狀態

  • 進程是程序的一次執行,在這個執行過程中,有事進程正在被CPU處理,有時又需要等待CPU服務,可見,進程的狀態是會有各種變化。爲了方便對各個進程的管理,操作系統需要將進程合理的劃分爲幾種狀態。
  • 在這裏插入圖片描述
  • 進程運行結束(或者由於bug導致進程無法繼續執行下去,比如數組越界錯誤),需要撤銷進程。
  • 操作系統需要完成撤銷進程相關的工作。完成將分配給進程的資源回收,撤銷進程PCB等工作

2. 進程的狀態–另外兩種狀態

在這裏插入圖片描述
在這裏插入圖片描述

3. 進程狀態的轉換

在這裏插入圖片描述

  • 阻塞態->就緒態
    • 不是進程自身能好控制的,是一種被動行爲
  • 運行態->阻塞態
    • 是一種進程自身做出的主動行爲
  • 注意
    • 不能由阻塞態直接轉換爲運行態,也不能由就緒態直接轉換爲阻塞態
    • 因爲進入阻塞態是進程主動請求的,必然需要進程在運行時才能發出這種請求

知識回顧

在這裏插入圖片描述

3. 進程控制

在這裏插入圖片描述

1. 什麼是進程控制

  • 主要功能
    • 對系統中的所有進程實施有效的管理
    • 具有創建新進程,撤銷已有進程,實現進程狀態轉換等功能
  • 簡化理解:
    • 進程控制就是要實現進程狀態轉換
      在這裏插入圖片描述

2. 如何實現進程控制

在這裏插入圖片描述
在這裏插入圖片描述

  • 原語實現進程控制。原語的特點是執行期間不允許中斷,只能一氣呵成
  • 這種不可被中斷的操作即原子操作
  • 原語採用“關中斷指令”和“開中斷指令”實現
  • 顯然,關/開中斷指令的權限非常大,必然是隻允許在核心態下執行的特權指令
    在這裏插入圖片描述

3. 進程控制相關的原語

  • 更新PCB中的信息(如修改進程狀態標誌,將運行環境保存到PCB,從PCB恢復運行環境)
    • 所有的進程控制原語一定都會修改進程狀態標誌
    • 剝奪當前運行進程的CPU使用權必然需要保存其運行環境
    • 某進程開始運行前必然要恢復期運行環境
  • 將PCB插入合適的隊列
  • 分配/回收資源
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述

知識回顧

在這裏插入圖片描述

4. 進程通信

在這裏插入圖片描述

1. 什麼是進程通信?

  • 進程通信就是指進程之間的信息交換
  • 進程是分配系統資源的單位(包括內存地址空間),因此各進程擁有的內存地址空間相互獨立
  • 爲了保證安全,一個進程不能直接訪問另一個進程的地址空間
  • 但是進程之間的信息交換又是必須實現的。
  • 爲了保證進程間的安全通信,操作系統提供了一些方法
    在這裏插入圖片描述
    在這裏插入圖片描述

2. 進程通信——共享存儲

  • 兩個進程對共享空間的訪問必須是互斥的(互斥訪問通過操作系統提供的工具實現)
  • 操作系統只負責提供共享空間和同步互斥工具(如P,V操作)
  • 基於數據結構的共享:
    • 比如共享空間裏只能放一個長度爲10的數組。
    • 速度慢,限制多,是一種低級通信方式
  • 基於存儲區的共享:
    • 在內存中畫出一塊共享存儲區,數據的形式,存放位置都由進程控制,而不是操作系統。
    • 速度快,是一種高級通信方式
      在這裏插入圖片描述
      在這裏插入圖片描述

3. 進程通信——管道通信

  1. 管道只能採用半雙工通信,某一時間段內只能實現單向的傳輸,如果要實現雙向同時通信,則需要設置兩個管道
  2. 各進程要互斥的訪問管道
  3. 數據以字符流的形式寫入管道,當管道寫滿時,寫進程的write()系統調用將被阻塞,等待讀進程將數據取走,當讀進程將數據取走,當讀進程將數據全部取走後,管理變空,此時讀進程的read()系統調用將被阻塞
  4. 如果沒寫滿就不允許讀,如果沒讀空,就不允許寫
  5. 數據一旦被讀出,就從管道中被拋棄,這就意味着讀進程最多只能有一個,否則可能會有讀錯數據的情況
    在這裏插入圖片描述

4. 進程通信——消息傳遞

  • 進程間的數據交換以格式化的消息(Message) 爲單位。進程通過操作系統提供的“發送消息/接受消息”兩個原語進行數據交換。
    在這裏插入圖片描述

知識回顧

在這裏插入圖片描述

5. 線程概念與多線程模型

在這裏插入圖片描述

1. 什麼是線程,爲什麼要引入線程?

  • 還沒引入進程之前,系統中各程序只能串行執行
  • 進程是程序的一次執行。但這些功能顯然不可能是由一個程序順序處理就能實現的
  • 可以把線程理解爲“輕量級進程”
  • 線程是一個基本的CPU執行單元,也是程序執行流的最小單位
  • 引入線程之後,不僅是進程之間可以併發,進程內的各線程之間也可以併發,從而進一步提升了系統的併發度,使得一個進程內也可以併發處理各種任務(如QQ,視頻,文字聊天,傳文件)
  • 引入線程後,進程只作爲除CPU之外的系統資源的分配單元(如打印機,內存地址空間等都是分配給進程的)
  • 線程則作爲處理機的分配單元
    在這裏插入圖片描述

2. 引入線程機制後,有什麼變化?

在這裏插入圖片描述

3. 線程的屬性

在這裏插入圖片描述

4. 線程的實現方式

1. 用戶級線程(User-Level Thread,ULT)

  • 用戶級線程由應用程序通過線程庫實現。
  • 所有的線程管理工作都由應用程序負責(包括線程切換)
  • 用戶級線程中,線程切換可以在用戶態下即可完成,無需操作系統干預
  • 在用戶看來,是由多個線程,但是在操作系統內核看來,並意識不到線程的存在(用戶級線程對用戶不透明,對操作系統透明)
  • 可以這樣理解,“用戶級線程”就是“從用戶視角看能看到的線程
    在這裏插入圖片描述

2. 內核級線程(Kernel-Level Thread,KLT,又稱“內核支持的線程”)

  • 內核級線程的管理工作操作系統內核完成
  • 線程調度,切換等工作都是由內核負責,因此內核級線程的切換必然需要在核心態下才能完成
  • 可以這樣理解,“內核級線程”就是“從操作系統內核視角看能看到的線程在這裏插入圖片描述

3. 二者組合

在同時支持用戶級線程和內核級線程的系統中,可採用二者組合的方式,將n個用戶級線程映射到m個內核級線程上(n >= m)

  • 重點
    • 操作系統只“看得見”內核級線程,因此只有內核級線程纔是處理機分配的單位
      在這裏插入圖片描述

5. 多線程模型

在同時支持用戶級線程和內核級線程的系統中,由幾個用戶級線程映射到幾個內核級線程的問題引出了“多線程模型”問題

1. 多對一模型

  • 多對一模型:
    • 多個用戶及線程映射到一個內核級線程,每個用戶進程只對應一個內核級線程
  • 優點:
    • 用戶級線程的切換在用戶空間即可完成,不需要切換到核心態,線程管理的系統開銷小,效率高
  • 缺點:
    • 當一個用戶級線程被阻塞後,整個進程都會被阻塞,併發度不高,多個線程不可在多核處理級上並行運行
      在這裏插入圖片描述

2. 一對一模型

  • 一對一模型:
    • 一個用戶及線程映射到一個內核級線程,每個用戶進程有與用戶級線程同數量的內核級線程
  • 優點:
    • 當一個線程被阻塞後,別的線程還可以繼續執行,併發能力強,多線程可在多核處理機上並行執行
  • 缺點:
    • 一個用戶進程會佔用多個內核級線程,線程切換由操作系統內核完成,需要切換到核心態,因此線程管理的成本高,開銷大
      在這裏插入圖片描述

3. 多對多模型

  • 多對多模型:
    • n 用戶及線程映射到 m 個內核級線程(n>=m),每個用戶進程對應 m 個內核級線程
    • 克服了多對一模型併發度不高的缺點,又克服了一對一模型中一個用戶進程佔用太多內核級線程,開銷太大的缺點
      在這裏插入圖片描述

知識回顧

在這裏插入圖片描述

6 處理機調度概念,層次

在這裏插入圖片描述

1. 調度的基本概念

  • 當由一堆任務要處理,但由於資源有限,這些事情沒法同時處理。這就需要確定某種規則決定處理這些任務的順序,這就是“調度”研究的問題
  • 在多道程序系統中,進程的數量往往是多於處理機的個數的,這樣不可能同時並行的處理各個進程,處理機調度,就是從就緒隊列中按照一定的算法選擇一個進程將處理機分配給他運行,以實現進程的併發執行

2. 調度的三個層次——高級調度

由於內存空間有限,有時無法將用戶提交的作業全部放入內存,因此就需要確定某種規則來決定將作業調入內存的順序

  • 高度調度(作業調度)按一定的原則從外存上處於後備隊列的作業中挑選一個(或多個)作業,給他們分配內存等必要資源,並建立相應的進程(建立PCB),以使它們獲取競爭處理機的權利
  • 高級調度是輔存(外存)與內存之間的調度。每個作業只調入一次,調出一次。作業調入時會建立相應的PCB作業調出時才撤銷PCB。高級調度主要是指調入的問題,因爲只有調入的時機需要操作系統來確定,但調出的時機必然是作業運行結束才調出

3. 調度的三個層次——中級調度

引入了虛擬存儲技術之後,可將暫時不能運行的進程調至外存等待。等它重新具備了運行條件且內存又稍有空閒時,再重新調入內存

  • 這麼做的目的是爲了提高內存利用率系統吞吐量
  • 暫時調到外存等待的進程狀態爲掛起狀態。值得注意的是,PCB並不會一起調到外存,而是會常駐內存。PCB中會記錄進程數據在外存中的存放位置,進程狀態等信息,操作系統通過內存中的PCB來保持對各個進程的監控,管理。被掛起的進程PCB會被放到的掛起隊列
  • 中級調度(內存調度),就是要決定將哪個處於掛起狀態的進程重新調入內存
  • 一個進程可能會被多次調出,調入內存,因此中級調度發生的頻率要比高級調度更高

4. 調度的三個層次——低級調度

  • 低級調度(進程調度),其主要任務是按照某種方法和策略從就緒隊列中選取一個進程,將處理機分配給他
  • 進程調度是操作系統中最基本的一種調度,在一般的操作系統中都必須配置進程調度
  • 進程調度的頻率很高,一般幾十毫秒一次

5. 進程的掛起態與七狀態模型

  • 暫時調到外存等待的進程狀態爲掛起狀態(掛起態,suspend)
  • 掛起態又可以進一步細分爲就緒掛起,阻塞掛起兩種狀態
  • 五狀態模型->七狀態模型
    在這裏插入圖片描述
  • 注意“掛起”和“阻塞”的區別
    • 兩種狀態都是暫時不能獲得CPU的服務
    • 掛起態是將進程映像調到外存去了,而阻塞態下進程映像還在內存中
    • 有的操作系統會把就緒掛起,阻塞掛起分爲兩個掛起隊列,甚至會根據阻塞原因不同再把阻塞掛起進程進一步細分爲多個隊列

6. 三層調度的聯繫,對比

在這裏插入圖片描述

知識回顧

在這裏插入圖片描述

7. 進程調度的時機切換與過程調度方式

在這裏插入圖片描述

1. 進程調度的時機

進程調度(低級調度),就是按照某種算法從就緒隊列中選擇一個進程爲其分配處理機
進程在操作系統內核程序臨界區不能進行調度與切換

  • 需要進行進程調度與切換的情況
    • 當前運行的進程主動放棄處理機(有的系統中,只允許進程主動放棄處理機)
      • 進程正常終止
      • 運行過程中發生異常而終止、
      • 進程主動請求阻塞(如等待I/O)
    • 當前運行的進程被動放棄處理機(有的系統中,進程可以主動放棄處理機,當有更緊急的任務需要處理時,也會強行剝奪處理機(被動放棄)
      • 分給進程的時間片用完
      • 有更緊急的事需要處理(如I/O中斷)
      • 有更高優先級的進程進入就緒隊列
  • 不能進行進程調度與切換的情況
    • 處理中斷的過程中,中斷處理過程複雜,與硬件密切相關,很難做到在中斷處理過程中進行進程切換
    • 進程在操作系統內核程序臨界區中(但是進程在普通臨界區中時可以進行調度,切換的)
    • 原子操作過程中(原語),原子操作不可中斷,要一氣呵成(如之前講過的修改PCB中進程狀態標誌,並把PCB放到相應隊列

真題: 進程處於臨界區市不能進行處理機調度(錯誤)

  • 臨界資源:一個時間段內只允許一個進程使用的資源,各進程需要互斥的訪問臨界資源
  • 臨界區:訪問臨界資源的那段代碼
    內核程序臨界區一般是用來訪問某種內核數據結構的,比如進程的就緒隊列(由各就緒進程的PCB組成)
  • 內核程序臨界區訪問的臨界資源如果不盡快釋放的話,極有可能影響到操作系統內核的其他管理工作,因此在訪問內核程序臨界區期間不能進行調度與切換
  • 普通臨界區訪問的臨界資源不會直接影響操作系統內核的管理工作,因此在訪問普通臨界區時可以進行調度與切換

2. 進程調度的方式

1. 非剝奪調度方式

  • 又稱非搶佔方式
  • 只允許進程主動放棄處理機
  • 在運行過程中即便有更緊迫的任務到達,當前進程依然會繼續使用處理機,直到該進程終止或主動要求進入阻塞態
  • 實現簡單,系統開銷小但是無法及時處理緊急任務,適合於早期的批處理系統

3. 剝奪調度方式

  • 又稱搶佔方式
  • 當一個進程正在處理機上執行時,如果有一個更重要或更緊迫的進程需要使用處理機,則立即暫停正在執行的進程,將處理機分配給更重要緊迫的那個進程
  • 可以優先處理更緊急的進程,也可實現讓各進程按時間片輪流執行的功能(通過時鐘中斷),適合於分時操作系統,實時操作系統

4. 進程的切換與過程

“狹義的進程調度”與“進程切換”的區別:

  • 狹義的進程調度指的是從就緒隊列中選中一個要運行的進程。(這個進程可以是剛剛被暫停執行的進程,也可能是另一個進程,後一種情況就需要進程切換
  • 進程切換是指一個進程讓出處理機,由另一個進程佔用處理機的過程。
  • 廣義的進程調度包括了選擇一個進程和進程切換兩個步驟。

進程切換的過程主要完成了:

  • 對原來運行進程各種數據的保存
  • 對新的進程各種數據的恢復
  • (如:程序計數器,程序狀態字,各種數據寄存器等處理機現場信息,這些信息一般保存在進程控制塊)

注意:

  • 進程切換是有代價的,因此如果過於頻繁的進行調度,切換,必然會使整個系統的效率降低,使系統大部分時間都花在了進程切換上,而真正用於執行進程的時間減少

知識回顧

在這裏插入圖片描述

8. 調度算法的評價指標

在這裏插入圖片描述

1. CPU利用率

由於早期的CPU造價十分昂貴,因此人們會希望讓CPU儘可能多的工作

  • CPU利用率:指CPU“忙碌”的時間佔總時間的比例
  • 利用率 = 忙碌的時間 / 總時間(有的題目還會要求計算某種設備的利用率)

例子: 某計算機只支持單道程序,某個作業剛開始需要在CPU上運行5秒,
再用打印機打印輸出5秒,之後再執行5秒,才能結束。在此過程中,CPU利用率,打印機利用率分別是多少?

  • CPU利用率 = (5+5)/(5+5+5)= 66.66%
  • 打印機利用率 = 5 / 15 = 33.33%
  • 通常會考察多道程序併發執行的情況,可以用“甘特圖”來輔助計算

2. 系統吞吐量

對於計算機來說,希望能用儘可能少的時間處理完儘可能多的作業

  • 系統吞吐量:單位時間內完成作業的數量
  • 系統吞吐量 = 總共完成了多少道作業 / 總共花了多少時間

例子: 某計算機系統處理完10道作業,共花費100秒,則系統吞吐量爲?
10 / 100 = 0.1 道/秒

3. 週轉時間

對於計算機的用戶來說,很關心自己的作業從提交到完成花了多少時間

  • 週轉時間
    • 指從作業被提交給系統開始,到作業完成爲止的這段時間間隔
  • 週轉時間四個部分(後三項在一個作業的整個處理過程中,可能發生多次)
    • 作業在外存後備隊列上等待作業調度(高級調度)的時間
    • 進程在就緒隊列上等待進程調度(低級調度)的時間
    • 進程在CPU上執行的時間
    • 進程等待I/O操作完成的時間
  1. (作業)週轉時間 = 作業完成時間 - 作業提交時間 (對於用戶來說,更關心自己的單個作業的週轉時間)
  2. 平均週轉時間 = 各作業週轉時間之和 / 作業數 ( 對於操作系統來說,更關心繫統的整體表現,因此更關心所有作業週轉時間的平均值)
  3. 帶權週轉時間 = 作業週轉時間 / 作業實際運行的時間 = (作業完成時間 - 作業提交時間)/ 作業實際運行的時間
    • 帶權週轉時間必然是 >= 1
    • 帶權週轉時間與週轉時間都是越小越好
  4. 平均帶權週轉時間 = 各作業帶權週轉時間之和 / 作業數
  5. 對於週轉時間相同的兩個作業,實際運行時間長的作業在相同時間內被服務的時間更多,帶權週轉時間更小,用戶滿意度更高
  6. 對於實際運行時間相同的兩個作業,週轉時間短的帶權週轉時間更小,用戶滿意度更高

4. 等待時間

計算機的用戶希望自己的作業儘可能少的等待處理機

  • 等待時間
    • 指進程/作業處於等待處理機狀態時間之和,等待時間越長,用戶滿意度越低
      在這裏插入圖片描述
  • 對於進程來說,等待時間就是指進程建立後等待被服務的時間之和,在等待I/O完成的期間其實進程也是在被服務的,所以不計入等待時間。
  • 對於作業來說,不僅要考慮建立進程後的等待時間還要加上作業在外存後備隊列中等待的時間
  • 一個作業總共需要被CPU服務多久,被I/O設備服務多久一般是確定不變的,因此調度算法其實只會影響作業/進程的等待時間。當然,與前面指標類似,也有“平均等待時間”來評價整體性能。

5. 響應時間

  • 對於計算機用戶來說,會希望自己的提交的請求(比如通過鍵盤輸入一個調試命令)儘早的開始被系統服務,迴應。
  • 響應時間,指從用戶提交請求首次產生響應所用的時間

知識回顧

在這裏插入圖片描述

9. 調度算法

在這裏插入圖片描述
Tips: 各種調度算法的學習思路

  • 算法思想
  • 算法規則
  • 這種調度算法是用於作業調度還是進程調度?
  • 搶佔式?非搶佔式?
  • 優點和缺點
  • 是否會導致飢餓(某進程/作業長期得不到服務)

1. 先來先服務(FCFS, First Come First Serve)

  • 算法思想
    • 主要從“公平”的角度考慮(類似於我們生活中排隊買東西的例子)
  • 算法規則
    • 按照作業/進程到達的先後順序進行服務
  • 用於作業/進程調度
    • 用於作業調度時,考慮的是哪個作業先到達後備隊列;
    • 用於進程調度時,考慮的是哪個進程先到達就緒隊列
  • 是否可搶佔
    • 非搶佔式的算法
  • 優缺點
    • 優點:公平,算法實現簡單
    • 缺點:排在長作業(進程)後面的短作業需要等待很長時間,帶權週轉時間很大,對短作業來說用戶體驗不好
    • FCFS算法對長作業有利,對短作業不利
  • 是否會導致飢餓
    • 不會

例子:

  • 各進程到達就緒隊列的時間,需要的運行時間如下表所示,使用先來先服務調度算法,計算各進程的等待時間,平均等待時間,週轉時間,平均週轉時間,帶權週轉時間,平均帶權週轉時間。
    在這裏插入圖片描述
  • 先來先服務調度算法:
    • 按照到達的先後順序調度,事實上就是等待時間越久的越優先得到服務。
    • 因此,調度順序爲:P1->P2->P3->P4
  1. 週轉時間 = 完成時間 - 到達時間(P1=7-0=7􏱦;P2=11-2=9􏱦;P3=12-4=8􏱦;P4=16-5=11)
  2. 帶權週轉時間 = 週轉時間 / 運行時間(P1=7/7=1􏱦;P2=9/4=2.25􏱦;P3=8/1=8;􏱦P4=11/4=2.75)
  3. 等待時間 = 週轉時間 - 運行時間(P1=7-7=0;􏱦P2=9-4=5;􏱦P3=8-1=7;􏱦P4=11-4=7)
  4. 注意:本例中的進程都是純計算型的進程,一個進程到達後要麼在等待,要麼在運行。如果是又有計算,又有I/O操作的進程,其等待時間就是週轉時間 - 運行時間 - I/O操作的時間
  5. 平均週轉時間 = (7+9+8+11)/4 = 8.75
  6. 平均帶權週轉時間 = (1+2.25+8+2.75)/4 = 3.5
  7. 平均等待時間 = (0+5+7+7)/4 = 4.75

2. 短作業優先(SJF,Shortest Job First)

  • 算法思想
    • 追求最少的平均等待時間,最少的平均週轉時間,最少的平均帶權週轉時間
  • 算法規則
    • 最短的作業/進程優先得到服務(所謂“最短”,是指要求服務時間最短)
  • 用於作業/進程調度
    • 即可用於作業調度,也可用於進程調度。用於進程調度時稱爲“短進程優先(SPF,Shortest Process First)算法”
  • 是否可搶佔
    • SJF和SPF時非搶佔式的算法。但是也有搶佔式的版本——最短剩餘時間優先算法(SRTN,Shortest Remaining Time Next)
  • 優缺點
    • 優點:“最短的”平均等待時間,平均週轉時間
    • 缺點:不公平,對短作業有利,對長作業不利。可能產生飢餓現象。作業/進程的運行時間是由用戶提供的,並不一定真實,不一定能做到真正的短作業優先
  • 是否會導致飢餓
    • 會,如果源源不斷的有短作業/進程到來,可能使長作業/進程長時間得不到服務,產生“飢餓”現象,如果一直得不到服務,則稱爲“餓死

例子:

  • 各進程到達就緒隊列的時間,需要的運行時間如下表所示,使用非搶佔式短作業優先調度算法,計算各進程的等待時間,平均等待時間,週轉時間,平均週轉時間,帶權週轉時間,平均帶權週轉時間。
    在這裏插入圖片描述
  • 短作業/進程優先調度算法:
    • 每次調度時選擇當前已到達運行時間最短的作業/進程
    • 因此,調度順序爲:P1->P3->P2->P4
    • 對比FCFS算法的結果,顯然SPF算法的平均等待/週轉/帶權週轉時間都要更低
  1. 週轉時間 = 完成時間 - 到達時間(P1=7-0=7􏱦;P3=8-4=4􏱦;P2=12-2=10;􏱦P4=16-5=11)
  2. 帶權週轉時間 = 週轉時間 / 運行時間(P1=7/7=1􏱦;P3=4/1=4􏱦;P2=10/4=2.5􏱦;P4=11/4=2.75)
  3. 等待時間 = 週轉時間 - 運行時間(P1=7-7=0􏱦;P3=4-1=3􏱦;P2=10-4=6􏱦;P4=11-4=7)
  4. 平均週轉時間 = (7+4+10+11)/4 = 8
  5. 平均帶權週轉時間 = (1+4+2.5+2.75)/4 = 2.56
  6. 平均等待時間 = (0+3+6+7)/4 = 4
  • 注意細節
    1. 如果題目中未特別說明,所提到的“短作業/進程優先算法”默認非搶佔式
    2. 所有進程同時可運行時,採用SJF調度算法的平均等待時間,平均週轉時間最少
    3. 搶佔式的短作業/進程優先調度算法(最短剩餘時間優先,SRNT算法)的平均等待時間,平均週轉時間最少
    4. 雖然嚴格來講,SJF的平均等待時間,平均週轉時間並不一定最少,但相比於其他算法(如FCFS),SJF依然可以獲得較少的平均等待時間,平均週轉時間

3. 高響應比優先(HRRN,Highest Response Ratio Next)

  • 算法思想
    • 綜合考慮作業/進程的等待時間和要求服務的時間
  • 算法規則
    • 在每次調度的時候先計算各個作業/進程的響應比,選擇響應比最高的作業/進程爲其服務
    • 響應比 = (等待時間+要求服務時間)/ 要求服務時間
    • 響應比 >= 1
  • 用於作業/進程調度
    • 即可用於作業調度,也可用於進程調度。
  • 是否可搶佔
    • 非搶佔式的算法。因此只有當前運行的作業/進程主動放棄處理機時,才需要調度,才需要計算響應比
  • 優缺點
    • 綜合考慮了等待時間和運行時間(要求服務時間)
    • 等待時間相同時,要求服務時間短的優先(SJF 的優點)
    • 要求服務時間相同時,等待時間長的優先(FCFS的優點)
    • 對於長作業來說,隨着等待時間越來越久,其響應比也會越來越大,從而避免了長作業飢餓的問題
  • 是否會導致飢餓
    • 不會

例子:

  • 各進程到達就緒隊列的時間,需要的運行時間如下表所示,使用高響應比優先調度算法,計算各進程的等待時間,平均等待時間,週轉時間,平均週轉時間,帶權週轉時間,平均帶權週轉時間
    在這裏插入圖片描述
  • 高響應比優先算法:
    • 非搶佔式的調度算法,只有當前運行的進程主動放棄CPU時(正常/異常完成,或主動阻塞),才需要進行調度,調度時計算所有就緒進程的響應比選響應比最高的進程上處理機。在這裏插入圖片描述
  • 0時刻:只有P1到達就緒隊列,P1上處理機
  • 7時刻(P1主動放棄CPU):就緒隊列中有P2(響應比=(5+4)/4=2.25),P3((3+1)/1=4),P4((2+4)/4=1.5)
    • P2和P4要求服務時間一樣,但P2等待時間長,所以必然是P2響應比更大
  • 8時刻(P3完成):P2(2.5),P4(1.75)
  • 12時刻(P2完成):就緒隊列中只剩下P4

知識回顧

在這裏插入圖片描述
注意:

  • 這幾種算法主要關心對用戶的公平性,平均週轉時間,平均等待時間等評價系統整體性能的指標,但是不關心“響應時間”,也並不區分任務的緊急程度,因此對於用戶來說,交互性很糟糕。
  • 因此這三種算法一般適合用於早期的批處理系統,FCFS算法也常結合其他算法使用,在現在也扮演着很重要的角色。

在這裏插入圖片描述

4. 時間片輪轉

  • 算法思想
    • 公平的,輪流的爲各個進程服務,讓每個進程在一定時間間隔內都可以得到響應
  • 算法規則
    • 按照各進程到達就緒隊列的順序,輪流讓各個進程執行一個時間片,若進程未在一個時間片內執行完,則剝奪處理機,將進程重新放到就緒隊列隊尾重新排隊
  • 用於作業/進程調度
    • 用於進程調度(只有作業放入內存建立了相應的進程後,才能被分配處理機時間片)
  • 是否可搶佔
    • 若進程未能在時間片內運行完,將被強行剝奪處理機使用權,因此時間片輪轉調度算法屬於搶佔式的算法。由時鐘裝置發出來通知CPU時間片已到
  • 優缺點
    • 優點:公平,響應快,適用於分時操作系統
    • 缺點:由於高頻率的進程切換,因此有一定開銷;不區分任務的緊急程度
    • 要求服務時間相同時,等待時間長的優先(FCFS的優點)
  • 是否會導致飢餓
    • 不會

5. 優先級調度算法

  • 算法思想
    • 隨着計算機的發展,特別是實時操作系統的出現,越來越多的應用場景需要根據任務的緊急程度來決定處理順序
  • 算法規則
    • 每個作業/進程有各自的優先級,調度時選擇優先級最高的作業/進程
  • 用於作業/進程調度
    • 既可用於作業調度,也可用於進度調度。甚至,還會用於在之後學習的I/O調度中
  • 是否可搶佔
    • 搶佔式,非搶佔式都有
    • 區別在於:
      • 非搶佔式只需在進程主動放棄處理機時進行調度即可
      • 搶佔式還需在就緒隊列變化時,檢查是否會發生搶佔
  • 優缺點
    • 優點:用優先級區分緊急程度,重要程度,適用於實時操作系統。可靈活的調整對各種作業/進程的偏好程度
    • 缺點:若源源不斷的有高優先級進程到來,則可能導致飢餓
  • 是否會導致飢餓
  • 補充
    • 就緒隊列未必只有一個,可以按照不同優先級來組織。另外,也可以把優先級高的進程排在更靠近隊頭的位置
    • 根據優先級是否可以動態改變,可將優先級分爲靜態優先級和動態優先級兩種
    • 靜態優先級:創建進程時確定,之後一直不變
    • 動態優先級:創建進程時有一個初始值,之後會根據情況動態的調整優先級
  • 如何合理的設置各類進程的優先級?
    • 系統進程優先級 高於 用戶進程
    • 前臺進程優先級 高於 後臺進程
    • 操作系統更偏好I/O型進程(或稱i/O繁忙型進程)
    • I/O設備和CPU可以並行工作,如果優先讓I/O繁忙型進程優先運行的話,則越有可能讓I/O設備儘早的投入工作,則資源利用率,系統吞吐量都會得到提升
    • 注意:與I/O型進程相對的是計算型進程(或稱CPU繁忙型進程)
  • 如果採用的是動態優先級,什麼時候應該調整?
    • 可以從追求公平,提升資源利用率等角度考慮
    • 如果某進程在就緒隊列中等待了很長時間,則可以適當提升其優先級
    • 如果某進程佔用處理機運行了很長時間,則可適當降低其優先級
    • 如果發現一個進程頻繁的進行I/O操作,則可適當提升其優先級

6. 多級反饋隊列調度算法

  • 算法思想
    • 對其他調度算法的折中權衡
  • 算法規則
    • 設置多級就緒隊列,各級隊列優先級從高到低,時間片從小到大
    • 新進程到達時先進入第一級隊列,按FCFS原則排隊等待被分配時間片,若用完時間片進程還未結束,則進程進入下一級隊列隊尾。如果此時已經是在最下級的隊列,則重新放回該隊列隊尾
    • 只有第K級隊列爲空時,纔會爲K+1級隊頭的進程分配時間片
  • 用於作業/進程調度
    • 用於進度調度
  • 是否可搶佔
    • 搶佔式的算法。在K級隊列的進程運行過程中,若更上級的隊列(1~K-1級)中進入了一個新進程,則由於新進程處於優先級更高的隊列中,因此新進程會搶佔處理機,原來運行的進程放回K級隊列隊尾
  • 優缺點
    • 對各類型進程相對公平(FCFS的優點)
    • 每個新到達的進程都可以很快就得到響應(RR的優點)
    • 短進程只用較少的時間就可完成(SPF的優點)
    • 不必實現估計進程的運行時間(避免用戶作假)
    • 可靈活的調整對各類進程的偏好程度,比如CPU密集型進程,I/O密集型進程(拓展:可以將因I/O而阻塞的進程重新放回原隊列,這樣I/O型進程就可以保持較高優先級)
  • 是否會導致飢餓

知識回顧

在這裏插入圖片描述
注意:
比起早期的批處理操作系統來說,由於計算機造價大幅降低,因此之後出現的交互式操作系統(包括分時操作系統,實時操作系統等)更注重系統的響應時間,公平性,平衡性等指標。而這幾種算法恰好也能較好的滿足交互式系統的需求。因此這三種算法適合於交互式系統。(比如UNIX使用的就是多級反饋隊列調度算法)

10. 進程同步與進程互斥

1. 什麼是進程同步

  • 知識點回顧:
    • 進程具有異步性的特徵。
    • 異步性是指,各併發執行的進程以各自獨立的,不可預知的速度向前推進
  1. 讀進程和寫進程併發的運行,由於併發必然導致異步性,因此“寫數據”和“讀數據”兩個操作執行的先後順序是不確定的,而實際應用中,又必須按照“寫數據->讀數據“的順序來執行的。
  2. 如何解決這種異步問題,就是”進程同步“所討論的內容
  3. 同步亦稱直接制約關係,它是指爲完成某種任務而建立的兩個或多個進程,這些進程因爲需要在某些位置上協調它們的工作次序而產生的制約關係。進程間的直接制約關係就是源於它們之間的相互合作。

2. 什麼是進程互斥

進程的“併發”需要“共享”的支持,各個併發執行的進程不可避免的需要共享一些系統資源(比如內存,又比如打印機,攝像頭這樣的I/O設備)
在這裏插入圖片描述

  • 我們把一個時間段內只允許一個進程使用的資源稱爲臨界資源。許多物理設備(比如攝像頭,打印機)都屬於臨界資源。此外還有許多變量,數據,內存緩衝區等都屬於臨界資源。

  • 對臨界資源的訪問,必須互斥的進行,互斥,亦稱間接制約關係進程互斥指當一個進程訪問某臨界資源時,另一個想要訪問該臨界資源的進程必須等待,當前訪問臨界資源的進程訪問結束,釋放該資源之後,另一個進程才能去訪問臨界資源。

  • 對臨界資源的互斥訪問,可以在邏輯上分爲如下四個部分:
    在這裏插入圖片描述

  • 注意:

    • 臨界區是進程中訪問臨界資源的代碼段
    • 進入區和退出區是負責實現互斥的代碼段
    • 臨界區也可稱爲“臨界段”
  • 爲了實現對臨界資源的互斥訪問,同時保證系統整體性能,需要遵循以下原則:

    • 空閒讓進。臨界區空閒時,可以允許一個請求進入臨界區的進程立即進入臨界區
    • 忙則等待。當已有進程進入臨界區時,其他試圖進入臨界區的進程必須等待
    • 有限等待。對請求訪問的進程,應保證能在有限時間內進入臨界區(保證不會飢餓)
    • 讓權等待。當進程不能進入臨界區時,應立即釋放處理機,防止進程忙等待

知識回顧

在這裏插入圖片描述

11. 進程互斥的軟件實現方法

在這裏插入圖片描述

1. 單標誌法

算法思想: 兩個進程在訪問完臨界區後會把使用臨界區的權限轉交給另一個進程。也就是說每個進程進入臨界區的權限只能被另一個進程賦予

int turn = 0;	// turn 表示當前允許進入臨界區的進程號

P0 進程:
while(turn !=0);	// 1
critical section;	// 2
turn = 1;			// 3
remainder section;	// 4

P1 進程:
while(turn !=1);	// 5 進入區
critical section;	// 6 臨界區
turn = 0;			// 7 退出區
remainder section	// 8 剩餘區
  • turn 的初值爲0,即剛開始只允許0號進程進入臨界區
  • 若P1先上處理機運行,則會一直卡在第5點。直到P1的時間片用完,發生調度,切換P0上處理機運行
  • 代碼1 不會卡住P0,P0可以正常訪問臨界區,在P0訪問臨界區期間即時切換回P1,P1依然會卡在第5點
  • 只有P0在退出區將turn改爲1後,P1才能進入臨界區
  • 因此,該算法可以實現“同一時刻最多隻允許一個進程訪問臨界區”

turn表示當前允許進入臨界區的進程號,而只有當前允許進入臨界區的進程在訪問了臨界區之後,纔會修改turn的值,也就是說,對於臨界區的訪問,一定時按P0->P1->P0->P1->…這樣輪流訪問。這種必須“輪流訪問”帶來的問題是,如果此時允許進入臨界區的進程是P0,而P0一直不訪問臨界區,那麼雖然此時臨界區空閒,但是並不允許P1訪問。
因此,單標誌法存在的主要問題是:違背“空閒讓進”原則

2. 雙標誌先檢查法

算法思想: 設置一個布爾型數組 flag[] ,數組中各個元素用來標記各進程想進入臨界區的意願,比如“flag[0] = true”意味着0號進程P0現在想要進入臨界區。每個進程在進入臨界區之前先檢查當前有沒有別的進程想進入臨界區,如果沒有,則把自身對應的標誌flag[i]設爲true,之後開始訪問臨界區。
在這裏插入圖片描述
若按照152637…的順序執行,P0和P1將會同時訪問臨界區。
因此,雙標誌先檢查法的主要問題是:違反“忙則等待”原則
原因在於,進入區的“檢查“和”上鎖“兩個處理不是一氣呵成。”檢查“後,”上鎖“前可能發生進程切換。

3. 雙標誌後檢查法

算法思想: 雙標誌先檢查法的改版。前一個算法的問題是先“檢查”後“上鎖”,但是這兩個操作又無法一氣呵成,因此導致了兩個進程同時進入臨界區的問題。因此,人們又想到先“上鎖”後“檢查”的方法,來避免上述問題。
在這裏插入圖片描述
若按照1526…的順序執行,P0和P1將都無法進入臨界區
因此,雙標誌後檢查法雖然解決了“忙則等待”的問題,但是又違背了“空閒讓進”和“有限等待”原則,會因各進程都長期無法訪問臨界資源而產生“飢餓”現象
兩個進程都爭着想進入臨界區,但是誰也不讓誰,最後誰都無法進入臨界區

4. Peterson 算法

算法思想: 雙標誌後檢查法中,兩個進程都爭着想進入臨界區,但是誰也不讓誰,最後誰都無法進入臨界區。Gary L.Peterson 想到一種方法,如果雙方都想爭着想進入臨界區,那可以讓進程嘗試“孔融讓梨”,主動讓對方先使用臨界區。
在這裏插入圖片描述

  • 進入區:
    • 主動爭取
    • 主動謙讓
    • 檢查對方是否也想使用,且最後一次是不是自己說了“客氣話”

Peterson 算法用軟件方法解決了進程互斥問題,遵循了空閒讓進,忙則等待,有限等待三個原則,但是依然未遵循讓權等待的原則

知識回顧

在這裏插入圖片描述

12. 進程互斥的硬件實現方法

在這裏插入圖片描述

1. 中斷屏蔽方法

利用“開/關中斷指令”實現(與原語的實現思想相同,即在某進程開始訪問臨界區到結束訪問爲止都不允許被中斷,也就不能發生進程切換,因此也不可能發生兩個同時訪問臨界區的情況)

  • 關中斷
    • 關中斷後即不允許當前進程被中斷,也必然不會發生進程切換
  • 開中斷
    • 直到當前進程訪問完臨界區,再執行開中斷指令,纔有可能有別的進程上處理機並訪問臨界區
  • 優點:簡單,高效
  • 缺點:不適用於多處理機;只適用於操作系統內核進程,不適用於用戶進程(因爲開/關中斷指令只能運行在內核態,這組指令如果能讓用戶隨意使用會很危險)

2. TestAndSet 指令

  • 簡稱TS指令,也有地方稱爲 TestAndSetLock 指令,或 TSL 指令
  • TSL 指令是用硬件實現的,執行的過程不允許被中斷,只能一氣呵成。以下是用C語言描述的邏輯
// 布爾型共享變量 lock 表示當前臨界區是否被加鎖
// true 表示已加鎖,false 表示未加鎖
bool TestAndSet(bool * lock){
	bool old;
	old = *lock;	// old用來存放lock 原來的值
	*lock = true;	// 無論之前是否已加鎖,都將lock設爲true
	return old;		// 返回lock原來的值
}
// 以下是使用 TSL 指令實現互斥的算法邏輯
while(TestAndSet(&lock));//"上鎖“並”檢查“
臨界區代碼段...
lock = false;	//"解鎖“
剩餘區代碼段...
  • 若剛開始 lock 是 false,則 TSL 後 old 返回的值爲 true,while 循環條件滿足,會一直循環,直到當前訪問臨界區的進程在退出區進行“解鎖”。
  • 相比軟件實現方法,TSL 指令把“上鎖”和“檢查”操作用硬件的方式變成了一氣呵成的原子操作。
  • 優點:實現簡單,無需像軟件實現方法那樣嚴格檢查是否會有邏輯漏洞;適用於多處理機環境
  • 缺點:不滿足“讓權等待”原則,暫時無法進入臨界區的進程會佔用CPU並循環執行TSL指令,從而導致“忙等”。

3. Swap 指令

  • 有的地方也叫 Exchange 指令,或簡稱 XCHG 指令
  • Swap 指令是用硬件實現的,執行的過程不允許被中斷,只能一氣呵成。以下是用C語言描述的邏輯
// Swap 指令的作用是交換兩個變量的值
Swap(bool *a,bool *b){
	bool temp;
	temp = *a;
	*a = *b;
	*b = temp;
}

// 以下是用 Swap 指令實現互斥的算法邏輯
// lock 表示當前臨界區是否被加鎖
bool old = true;
while(old == true)
	Swap(&lock,&old);
臨界區代碼段...
lock = false;
剩餘區代碼段...
  • 邏輯上來看 Swap 和 TSL 並無太大區別,都是先記錄下此時臨界區是否已經被上鎖(記錄在 old 變量上),再將上鎖標記 lock 設置爲 true,最後檢查 old ,如果 old 爲 false 則說明之前沒有別的進程對臨界區上鎖,則可跳出循環,進入臨界區。
  • 優點:實現簡單,無需像軟件實現方法那樣嚴格檢查是否會有邏輯漏洞;適用於多處理機環境
  • 缺點:不滿足“讓權等待”原則,暫時無法進入臨界區的進程會佔用CPU並循環執行TSL指令,從而導致“忙等”。

知識回顧

在這裏插入圖片描述

13. 信號量機制

複習

  • 進程互斥的四種軟件實現方式(單標誌法,雙標誌先檢查,雙標誌後檢查,Peterson算法)
  • 進程互斥的三種硬件實現方式(中斷屏蔽方法,TS/TSL指令,Swap/XCHG指令)
  • 在雙標誌先檢查法中,進入區的“檢查”,“上鎖”操作無法一氣呵成,從而導致了兩個進程有可能同時進入臨界區的問題
  • 所有的解決方案都無法實現“讓權等待”
    在這裏插入圖片描述
    信號量機制:
  • 用戶進程可以通過使用操作系統提供的一對原語來對信號量進行操作,從而很方便的實現了進程互斥,進程同步。
  • 信號量其實就是一個變量(可以是一個整數,也可以是更復雜的記錄型變量),可以用一個信息量來表示系統中某種資源的數量,比如:系統中只有一臺打印機,就可以設置一個初值爲1的信號量
  • 原語是一種特殊的程序段,其執行只能一氣呵成,不可被中斷。原語是由關中斷/開中斷指令實現的,軟件解決方案的主要問題是由“進入區的各種操作無法一氣呵成”,因此如果能把進入區,退出區的操作都用“原語”實現,使這些操作能“一氣呵成”就能避免問題。
  • 一對原語wait(S) 原語和 signal(S) 原語,可以把原語理解爲我們自己寫的函數,函數名分別爲 wait 和 signal ,括號裏的信息量S 其實就是函數調用時傳入的一個參數。
  • wait,signal 原語常簡稱爲 P,V操作(來自荷蘭語 proberen 和 verhogen)。因此,做題的時候常把 wait(S),signal(S) 兩個操作分別寫爲 P(S),V(S)

1. 信號量機制——整型信號量

用一個整數型的變量作爲信號量,用來表示系統中某種資源的數量

  • 例子: 某計算機系統中有一臺打印機
int S = 1;	// 初始化整型信號量S,表示當前系統中可用的打印機資源數

void wait(int S){	// wait 原語,相當於“進入區”
	while(S <= 0);	// 如果資源數不夠,就一直循環等待
	S=S-1// 如果資源數夠,則佔用一個資源
}

void signal(int S){	// signal 原語,相當於“退出區”
	S=S+1// 使用完資源後,在退出區釋放資源
}

進程P0:
...
wait(S);	// 進入區,申請資源
使用打印機資源...	// 臨界區,訪問資源
signal(S);	// 退出區,釋放資源
...

進程P1:
...
wait(S);
使用打印機資源
signal(S);
...

進程Pn:
...
wait(S);
使用打印機資源
signal(S);
...
  • 與普通整數變量的區別:
    • 對信號量的操作只有三種:初始化,P操作,V操作
  • “檢查”和“上鎖”一氣呵成,避免了併發,異步導致的問題
  • 存在的問題:不滿足“讓權等待”原則,會發生“忙等”

2. 信號量機制——記錄型信號量

  • 整型信號量的缺陷時存在“忙等”問題,因此人們又提出了“記錄型信號量”,即用記錄型數據結構表示的信號量。
  • 如果剩餘資源數不夠,使用block原語使進程從運行態進入阻塞態,並把掛到信號量S的等待隊列(即阻塞隊列)中
  • 釋放資源後,若還有別的進程在等待這種資源,則使用 wakeup 原語喚醒等待隊列中的一個進程,該進程從阻塞態變爲就緒態
// 記錄型信號量的定義
typedf struct{
	int value;	//剩餘資源數
	Struct process *L;	//等待隊列
}semaphore;

// 某進程需要使用資源時,通過 wait 原語申請
void wait (semaphore S){
	S.value--;
	if(S.value < 0){
		block (S.L);
	}
}

// 進程使用完資源後,通過 signal 原語釋放
void signal(semaphore S){
	s.value++;
	if(S.value <= 0){
		wakeup(S.L);
	}
}
  • 在考研題目中 wait(S),signal(S) 也可以記爲 P(S),V(S),這對原語可用於實現系統資源的“申請”和“釋放”。
  • S.value 的初值表示系統中某種資源的數目
  • 對信號量S的一次 P 操作意味着進程請求一個單位的該類資源,因此需要執行S.value–,表示資源數減1,當S.value<0時表示該類資源已分配完畢,因此進程應調用 block 原語進行自我阻塞(當前運行的進程從運行態->阻塞態),主動放棄處理機,並插入該類資源的等待隊列S.L中,可見,該機制遵循了“讓權等待”原則,不會出現“忙等”現象。
  • 對信號量S的一次V操作意味着進程釋放一個單位的該類資源,因此需要執行S.value++,表示資源數加1,若加1後仍是S.value<=0,表示依然有進程在等待該類資源,因此應調用 wakeup 原語喚醒等待隊列中的第一個進程(被喚醒進程從阻塞態->就緒態)。

知識回顧

在這裏插入圖片描述

14. 用信號量機制實現進程互斥,同步,前驅關係

在這裏插入圖片描述

1. 信號量機制實現進程互斥

  1. 分析併發進程的關鍵活動,劃定臨界區(如:對臨界資源打印機的訪問就應放在臨界區)
  2. 設置互斥信號量 mutex,初值爲1
  3. 在臨界區之前執行 P(mutex)
  4. 在臨界區之後執行 V(mutex)
    在這裏插入圖片描述
// 信號量機制實現互斥
// 要會自己定義記錄型信號量,但如果題目中沒特別說明,可以把信號量的聲明簡寫成這種形式
semaphore mutex=1; //初始化信號量

P1(){
	...
	P(mutex);	// 使用臨界資源前需要加鎖
	臨界區代碼段。。。
	V(mutex);	// 使用臨界資源後需要解鎖
	...
}

P2(){
	...
	P(mutex);
	臨界區代碼段。。。
	V(mutex);
	...
}
  • 注意:
    • 對不同的臨界資源需要設置不同的互斥信號量。
    • P,V操作必須成對出現,缺少P(mutex)就不能保證臨界資源的互斥訪問。缺少 V(mutex) 會導致資源永不被釋放,等待進程永不被喚醒

2. 信號量機制實現進程同步

進程同步:要讓各併發進程按要求有序的推進

  • 比如,P1,P2 併發執行,由於存在異步性,因此二者交替推進的次序時不確定的
  • 若 P2 的“代碼4”要基於 P1 的“代碼1”和“代碼2”的運行結果才能執行,那麼我們就必須保證“代碼4”一定是在“代碼2”之後纔會執行。
  • 這就是進程同步問題,讓本來異步併發的進程互相配合,有序推進。
P1(){
	代碼1;
	代碼2;
	代碼3}

P2(){
	代碼4;
	代碼5;
	代碼6}

用信號量實現進程同步:

  1. 分析什麼地方需要實現“同步關係”,即必須保證“一前一後”執行的兩個操作(或兩句代碼)
  2. 設置同步信號量S,初始爲0
  3. 在“前操作”之後執行 V(S)
  4. 在“後操作”之前執行 P(S)
// 信號量機制實現同步
semaphore S=0; // 初始化同步信號量,初始值爲0

P1(){
	代碼1;
	代碼2V(S);
	代碼3}

P2(){
	P(S);
	代碼4;
	代碼5;
	代碼6}
  • 若先執行到 V(S) 操作,則 S++ 後 S=1。之後當執行到 P(S) 操作時,由於 S=1, 表示有可用資源,會執行 S–, S 的值變回0,P2 進程不會執行 block 原語,而是繼續往下執行代碼4.
  • 若先執行到 P(S) 操作,由於 S=0,S-- 後 S=-1,表示此時沒有可用資源,因此P操作中會執行 block 原語,主動請求阻塞。之後當執行完代碼2,繼而執行 V(S) 操作,S++,使S變回0,由於此時有進程在該信號量對應的阻塞隊列中,因此會在 V 操作中執行 wakeup 原語,喚醒 P2 進程,這樣 P2 就可以繼續執行代碼4了

3. 信號量機制實現前驅關係

進程 P1 中有句代碼 S1,P2 中有句代碼 S2…P3…P6 中有句代碼 S6。這些代碼要求按如下前驅圖所示的順序來執行:
在這裏插入圖片描述
在這裏插入圖片描述
其實每一對前驅關係都是一個進程同步問題(需要保證一前一後的操作)因此,

  1. 要爲每一對前驅關係各設置一個同步變量
  2. 在“前操作”之後對相應的同步變量執行 V 操作
  3. 在“後操作”之前對相應的同步變量執行 P 操作

知識回顧

在這裏插入圖片描述

15. 生產者消費者問題

1. 問題描述

  • 系統中有一組生產者進程和一組消費者進程,生產者進程每次生產一個產品放入緩衝區,消費者進程每次從緩衝區中取出一個產品並使用。(注意:這裏的“產品”理解爲某種數據)
  • 生產者,消費者共享一個初始爲空,大小爲 n 的緩衝區。(剛開始空閒緩衝區的數量爲 n,非空閒緩衝區(產品)的數量爲0)
  • 只有緩衝區沒滿時,生產者才能把產品放入緩衝區,否則必須等待。(同步關係,緩衝區滿時,生產者要等待消費者取走產品)
  • 只有緩衝區不空時,消費者才能從中取出產品,否則必須等待。(同步關係,緩衝區空時,(即沒有產品時),消費者要等待生產者放入產品)
  • 緩衝區是臨界資源,各進程必須互斥的訪問。(互斥)
    在這裏插入圖片描述
    如何用信號量機制(P,V操作)實現生產者,消費者進程的這些功能呢?
    信號量機制可實現互斥設置初值爲1的互斥信號量),同步設置初值爲0的同步信號量(實現“一前一後”)),對一類系統資源的申請和釋放設置一個信號量,初始值即爲資源的數量(本質上也屬於“同步問題”,若無空閒資源,則申請資源的進程需要等待別的進程釋放資源後才能繼續往下執行))

PV操作題目分析步驟:

  1. 關係分析,找出題目中描述的各個進程,分析它們之間的同步,互斥關係
  2. 整理思路,根據各進程的操作流程確定P,V操作的大致順序。(生產者每次要消耗(P)一個空閒緩衝區,並生產(V)一個產品。消費者每次要消耗(P)一個產品。並釋放一個空閒緩衝區(V)。往緩衝區放入/取走產品需要互斥)
  3. 設置信號量,設置需要的信號量,並根據題目條件確定信號量初值。(互斥信號量初值一般爲1,同步信號量的初始值要看對應資源的初始值時多少)
semaphore mutex = 1;  // 互斥信號量,實現對緩衝區的互斥訪問
semaphore empty = n;  // 同步信號量,表示空閒緩衝區的數量
semaphore full = 0;  // 同步信號量,表示產品的數量,也即非空緩衝區的數量

2. 如何實現

  • 生產者,消費者共享一個初始爲空,大小爲 n 的緩衝區
  • 只有緩衝區沒滿時,生產者才能把產品放入緩衝區,否則必須等待
  • 只有緩衝區不空時,消費者才能從中取出產品,否則必須等待
  • 緩衝區時臨界資源,各進程必須互斥的訪問
semaphore mutex = 1;  // 互斥信號量,實現對緩衝區的互斥訪問
semaphore empty = n;  // 同步信號量,表示空閒緩衝區的數量
semaphore full = 0;  // 同步信號量,表示產品的數量,也即非空緩衝區的數量

producer(){
	while(1){
		生產一個產品;
		P(empty);  // 消耗一個空閒緩衝區
		P(mutex);
		把產品放入緩衝區; // 實現互斥是在同一進程中進行一對 PV 操作
		V(mutex);
		V(full);  // 增加一個產品
	}
} 

// 實現兩進程的同步關係,是在其中一個進程中執行 P ,另一進程中執行 V

consumer(){
	while(1){
		P(full);  // 消耗一個產品
		P(mutex);
		從緩衝區取出一個產品;
		V(mutex);
		V(empty):  // 增加一個空閒緩衝區
		使用產品;
	}
}

3. 思考:能否改變相鄰P,V操作的順序?

producer(){
	while(1){
		生產一個產品;
		P(mutex);  // 1	mutex 的 P 操作在前
		P(empty);  // 2
		把產品放入緩衝區; // 實現互斥是在同一進程中進行一對 PV 操作
		V(mutex);
		V(full);  // 增加一個產品
	}
} 

consumer(){
	while(1){
		P(mutex);  // 3
		P(full);  // 4
		從緩衝區取出一個產品;
		V(mutex);
		V(empty):  // 增加一個空閒緩衝區
		使用產品;  // 能否放到 PV 操作之間
	}
}
  • 若此時緩衝區內已經放滿產品,則 empty = 0, full = n
  • 則生產者進程執行 1 ,使 mutex 變爲 0 ,再執行 2,由於已沒有空閒緩衝區,因此生產者被阻塞。
  • 由於生產者阻塞,因此切換回消費者進程。消費者進程執行 3,由於 mutex 爲 0,即生產者還沒釋放對臨界資源的“鎖”,因此消費者也被阻塞
  • 這就造成了生產者等待消費者釋放空閒緩衝區,而消費者又等待生產者釋放臨界區的情況,生產者和消費者循環等待被對方喚醒,出現“死鎖”
  • 同樣的,若緩衝區中沒有產品,即 full = 0,empty = n。按 3 4 1 的順序執行就會發生死鎖
  • 因此,實現互斥的 P 操作一定要在實現同步的 P 操作之後。
  • V 操作不會導致進程阻塞,因此兩個 V 操作順序可以交換

知識回顧

PV操作題目分析步驟:

  1. 關係分析,找出題目中描述的各個進程,分析它們之間的同步,互斥關係
  2. 整理思路,根據各進程的操作流程確定P,V操作的大致順序。(生產者每次要消耗(P)一個空閒緩衝區,並生產(V)一個產品。消費者每次要消耗(P)一個產品。並釋放一個空閒緩衝區(V)。往緩衝區放入/取走產品需要互斥)
  3. 設置信號量,設置需要的信號量,並根據題目條件確定信號量初值。(互斥信號量初值一般爲1,同步信號量的初始值要看對應資源的初始值時多少)
  • 生產者消費者問題是一個互斥,同步的綜合問題
  • 對於初學者來說最難的是發現題目中隱含的兩對同步關係
  • 有時候是消費者需要等待生產者生產,有時候是生產者要等待消費者消費,這是兩個不同的“一前一後問題”,因此也需要設置兩個同步信號量
    在這裏插入圖片描述
    易錯點:
    • 實現互斥和實現同步的兩個 P 操作的先後順序

16. 多生產者-多消費者

1. 問題分析

桌子上有一隻盤子,每次只能向其中放入一個水果。爸爸專向盤子中放蘋果,媽媽專向盤子中放橘子,兒子賺等着吃盤子中的橘子,女兒專等着吃盤子中的蘋果。只有盤子空時,爸爸或媽媽纔可向盤子中放一個水果,僅當盤子中有自己需要的水果時,兒子或女兒可以從盤子中取出水果。

  1. 關係分析,找出題目中描述的各個進程,分析它們之間的同步,互斥關係
  2. 整理思路,根據各進程的操作流程確定 P,V 操作的大致順序。(互斥:在臨界區前後分別 PV 同步:前V後P)
  3. 設置信號量,設置需要的信號量,並根據題目條件確定信號量初值。(互斥信號量初值一般爲1,同步信號量的初始值要看對應資源的初始值是多少)
    在這裏插入圖片描述

互斥關係:(mutex = 1)
對緩衝區(盤子)的訪問要互斥的進行

同步關係(一前一後)

  1. 父親將蘋果放入盤子中,女兒才能取蘋果
  2. 母親將橘子放入盤子後,兒子才能取橘子
  3. 只有盤子爲空時,父親或母親才能放入水果(“盤子爲空”這個事件可以由兒子或女兒觸發,事件發生後才允許父親或母親放水果)

2. 如何實現

semaphore mutex = 1;	// 實現互斥訪問盤子(緩衝區)(問題:可不可以不用互斥信號量)
semaphore apple = 0;	// 盤子中有幾個蘋果
semaphore orange = 0;	// 盤子中有幾個橘子
semaphore plate = 1;	// 盤子中還可以放多少個水果

dad(){
	while(1){
		準備一個蘋果;
		P(plate);
		P(mutex);
		把蘋果放入盤子;
		V(mutex);
		V(apple);
	}
}

mom(){
	while(1){
		準備一個橘子;
		P(plate);
		P(mutex);
		把橘子放入盤子;
		V(mutex);
		V(orange);
	}
}

daughter(){
	while(1){
		P(apple);
		P(mutex);
		從盤中取出蘋果;
		V(mutex);
		V(apple);
		吃掉蘋果;
	}
}

son(){
	while(1){
		P(orange);
		P(mutex);
		從盤中取出橘子;
		V(mutex);
		V(plate);
		吃掉橘子;
	}
}

分析:

  • 剛開始,兒子,女兒進程即使上處理機運行也會被阻塞。
  • 如果剛開始是父親進程先上處理機運行,則父親P(plate),可以訪問盤子->母親P(plate),阻塞等待盤子->父親放入蘋果V(apple),女兒進程被喚醒,其他進程即使運行也都會阻塞,暫時不可能訪問臨界資源(盤子)->女兒P(apple),訪問盤子,V(plate),等待盤子的母親進程被喚醒->母親進程訪問盤子(其他進程暫時都無法進入臨界區)->…
  • 如果盤子(緩衝區)容量爲2,那麼父親P(plate),可以訪問盤子->母親P(plate),可以訪問盤子->父親在往盤子裏放蘋果,同時母親也可以往盤子裏放橘子。於是就出現了兩個進程同時訪問緩衝區的情況,有可能導致兩個進程寫入緩衝區的數據相互覆蓋的情況。因此,如果緩衝區大小大於1,就必須專門設置一個互斥信號量 mutex 來保證互斥訪問緩衝區。

結論:

  • 即使不設置專門的互斥變量 mutex,也不會出現多個進程同時訪問盤子的現象

原因在於:

  • 本題中的緩衝區大小爲1,在任何時刻,apple, orange, plate 三個同步信號量中最多隻有一個是1。因此在任何時刻,最多隻有一個進程的P操作不會被阻塞,並順利的進入臨界區。。。

知識回顧

總結:

  • 在生產者-消費者問題中,如果緩衝區大小爲1,那麼有可能不需要設置互斥信號量就可以實現互斥訪問緩衝區的功能。當然,這不是絕對的,要具體問題具體分析

建議:

  • 在考試中如果來不及仔細分析,可以加上互斥信號量,保證各進程一定會互斥的訪問緩衝區。但需要注意的是,實現互斥的P操作一定要在實現同步的P操作之後,否則可能引起“死鎖”。

PV 操作題目的解題思路

  1. 關係分析,找出題目中描述的各個進程,分析它們之間的同步,互斥關係
  2. 整理思路,根據各進程的操作流程確定 P,V 操作的大致順序。
  3. 設置信號量,設置需要的信號量,並根據題目條件確定信號量初值。(互斥信號量初值一般爲1,同步信號量的初始值要看對應資源的初始值是多少)

17. 吸菸者問題

1. 問題描述

假設一個系統有三個抽菸者進程一個供應者進程。每個抽菸者不停的捲菸並抽掉它,但是要捲起並抽掉一支菸,抽菸者需要有三種材料,菸草,紙和膠水。三個抽菸者中,第一個擁有菸草第二個擁有紙第三個擁有膠水。供應者進程無限的提供三種材料,供應者每次將兩種材料放桌子上,擁有剩下那種材料的抽菸者卷一根菸並抽掉它,並給供應者進程一個信號告訴完成了,供應者就會放另外兩種材料在桌上,這個過程一直重複(讓三個抽菸着輪流的抽菸

2. 問題分析

本質上這題也屬於“生產者-消費者”問題,更詳細的說應該是”可生產多種產品的單生產者-多消費者”。

  1. 關係分析,找出題目中描述的各個進程,分析它們之間的同步,互斥關係
  2. 整理思路,根據各進程的操作流程確定 P,V 操作的大致順序
  3. 設置信號量,設置需要的信號量,並根據題目條件確定信號量初值。(互斥信號量初值一般爲1,同步信號量的初始值要看對應資源的初始值是多少)

桌子可以抽象爲容量爲1的緩衝區,要互斥訪問

  • 組合一:紙+膠水
  • 組合二:菸草+膠水
  • 組合三:菸草+紙

同步關係(從事件的角度來分析):

  • 桌上有組合一:第一個抽菸者取走東西
  • 桌上有組合二:第二個抽菸者取走東西
  • 桌上有組合三:第三個抽菸者取走東西
  • 發出完成信號:供應者將下一個組合放到桌上

PV操作順序: “前V後P”

3. 如何實現

provider(){
	while(1){
		if(i==0){
			將組合一放桌上;
			V(offer1);
		}else if(i==1){
			將組合二放桌上;
			V(offer2);
		}else if(i==2){
			將組合三放桌上;
			V(offer3);
		}
		i = (i+1)%3;
		P(finish);
	}
}

// 緩衝區大小爲1,同一時刻,四個同步信號量中至少有一個的值爲1
semaphore offer1 = 0; // 桌上組合一的數量
semaphore offer2 = 0; // 桌上組合二的數量
semaphore offer3 = 0; // 桌上組合三的數量
semaphore finish = 0; // 抽菸是否完成
int i = 0;			  // 用於實現“三個抽菸者輪流抽菸”

smoker1 (){
	while(1){
		P(offer1);
		從桌上拿走組合一;捲菸;抽掉;
		V(finish);
	}
}

smoker2(){
	while(1){
		P(offer2);
		從桌上拿走組合二;捲菸;抽掉;
		V(finish);
	}
}

smoker3(){
	while(1){
		P(offer3);
		從桌上拿走組合三;捲菸;抽掉;
		V(finish);
	}
}

知識回顧

  • 吸菸者問題可以爲我們解決“可以生產多個產品的單生產者;問題提供一個思路
  • 值得吸取的精華是:”輪流讓各個吸菸者“必然需要“輪流的在桌上放上組合一,二,三”
  • 注意體會我們是如何用一個整型變量 i 實現這個“輪流”過程的
  • 如果題目改爲“每次隨機的讓一個吸菸者吸菸”,我們有應該如何用代碼寫出這個邏輯呢?

若一個生產者要生產多種產品(或者說會引發多種前驅事件),那麼各個V操作應該放在各自對應的“事件”發生之後的位置

18. 讀者-寫者問題

1. 問題描述

有讀者和寫者兩組併發進程,共享一個文件,當兩個或兩個以上的讀進程同時訪問共享數據時不會產生副作用,但若某個寫進程和其他進程(讀進程或寫進程)同時訪問共享數據時則可能導致數據不一致的錯誤。因此要求:

  • 允許多個讀者可以同時對文件執行讀操作
  • 值允許一個寫者往文件中寫信息
  • 任一寫者在完成寫操作之前不允許其他讀者或寫者工作
  • 寫者執行寫操作前,應讓已有的讀者和寫者全部退出
    在這裏插入圖片描述

2. 問題分析

兩類進程:寫進程,讀進程
互斥關係:寫進程——寫進程,寫進程——讀進程。讀進程與讀進程不存在互斥問題

  • 寫者進程和任何進程都互斥,設置一個互斥信號量 rw,在寫者訪問共享文件前後分別執行 P,V 操作。
  • 讀者進程和寫者進程也要互斥,因此讀者訪問共享文件前後也要對 rw 執行 P,V 操作。
  • 如果所有讀者進程在訪問共享文件之前都執行 P(rw) 操作,那麼會導致各個讀進程之間也無法同時訪問文件。

Key:讀者寫者問題的核心思想——怎麼處理該問題呢?
P(rw) 和 V(rw) 其實就是對共享文件的“加鎖”和“解鎖”。既然各個讀進程需要同時訪問,而讀進程與寫進程又必須互斥訪問,那麼我們可以讓第一個訪問文件的讀進程“加鎖”,讓最後一個訪問完文件的讀進程“解鎖”。可以設置一個整數變量 count 來記錄當前有幾個讀進程在訪問文件。

3. 如何實現

semaphore rw = 1;	// 用於實現對文件的互斥訪問。表示當前是否有進程在訪問共享文件
int count = 0;		// 記錄當前有幾個讀進程在訪問文件
semaphore mutex = 1;// 用於保證對count變量的互斥訪問
semaphore w = 1;	// 用於實現“寫優先”

writer(){
	while(1){
		P(w);
		P(rw);	// 寫之前“加鎖”
		寫文件...
		V(rw);	// 寫之後“解鎖”
		V(w);
	}
}

reader(){
	while(1){
		P(w);
		//(潛在的問題:只要有讀進程還在讀,寫進程就要一直阻塞等待,可能“餓死”。因此,這種算法中,讀進程是優先的)
		P(mutex);	// 各讀進程互斥訪問count 
		if(count == 0)
			P(rw);	//	第一個讀進程負責“加鎖“
		count++;	// 訪問文件的讀進程數+1
		V(mutex);
		V(w);
		讀文件..
		P(mutex);	// 各讀進程互斥訪問count
		count--;	// 訪問文件的讀進程數-1
		if(count == 0)
			V(rw);	// 最後一個讀進程負責“解鎖”
		V(mutex);
	}
}

思考:

  • 若兩個讀進程併發執行,則兩個讀進程有可能先後執行P(rw),從而使第二個讀進程阻塞的情況

如何解決:

  • 出現上述問題的原因在於讀count變量的檢查和賦值無法一氣呵成,因此可以設置另一個互斥信號量來保證各讀進程對count的訪問是互斥的。

分析以下併發執行P(w)的情況:

  • 讀者1->讀者2
  • 寫者1->寫者2
  • 寫者1->讀者1
  • 讀者1->寫者1->讀者2
  • 寫者1->讀者1->寫者2

結論:

  • 在這種算法中,連續進入的多個讀者可以同時讀文件;
  • 寫者和其他進程不能同時訪問文件;
  • 寫者不會飢餓,但也並不是真正的“寫優先”,而是相對公平的先來先服務原則。(有的書上把這種算法稱爲“讀寫公平法”)

知識回顧

  • 讀者-寫者問題爲我們解決複雜的互斥問題提供了一個參考思路。
  • 其核心思想在於設置了一個計數器count 用來記錄當前正在訪問共享文件的讀進程數。我們可以用count 的值來判斷當前進入的進程是否是第一個/最後一個讀進程,從而做出不同的處理
  • 另外,對count變量的檢查和賦值不能一氣呵成導致了一些錯誤,如果需要實現“一氣呵成”,自然應該想到用互斥信號量。
  • 最後,還要認真體會我們是如何解決“寫進程飢餓“問題的。

19. 哲學家進餐問題

1. 問題描述

一張圓桌上坐着5名哲學家,每兩個哲學家之間的桌上擺一根筷子,桌子的中間是一碗米飯。哲學家們傾注畢生的精力用於思考和進餐,哲學家在思考時,並不影響他人。只有當哲學家飢餓時, 才試圖拿起左、右兩根筷子(一根一根地拿起)。如果筷子已在他人手上,則需等待。飢餓的哲學家只有同時拿起兩根筷子纔可以開始進餐,當進餐完畢後,放下筷子繼續思考。

  1. 關係分析。系統中有5個哲學家進程,5位哲學 家與左右鄰居對其中間筷子的訪問是互斥關係。
  2. 整理思路。這個問題中只有互斥關係,但與之前 遇到的問題不同的事,每個哲學家進程需要同時 持有兩個臨界資源才能開始吃飯。如何避免臨界 資源分配不當造成的死鎖現象,是哲學家問題的精髓。
  3. 信號量設置。定義互斥信號量數組 chopstick[5]={l,l, 1,1,1}用於實現對5個筷子的互斥訪問。並對哲學家按0~4編號,哲學家 i 左邊的筷子編號爲 i ,右邊的筷子編號爲(i+l)%5。

2. 問題分析

  • 每個哲學家吃飯前依次拿起左、 右兩支筷子
  • 每位哲學家循環等待右邊的人放下筷子(阻塞),發生“死鎖”

3. 如何實現

如何防止死鎖的發生呢?

  • 可以對哲學家進程施加一些限制條件,比如最多允許四個哲學家同時進餐。這樣可以保證至少有一個哲學家是可以拿到左右兩隻筷子的
  • 要求奇數號哲學家先拿左邊的筷子,然後再拿右邊的筷子,而偶數號哲學家剛好相反。用這種方法可以保證如果相鄰的兩個奇偶號哲學家都想吃飯,那麼只會有其中一個可以拿起第一隻筷子,另一個會直接阻塞。這就避免了佔有一支後再等待另一隻的情況。
semaphore chopstick[5] ={1,1,1,1, 1}; 
semaphore mutex = 1;	// 互斥地取筷子
Pi ()(					// i號哲學家的進程
	while(1)(
		P(mutex);
		P (chopstick [ i] ) ;	//拿左
		P (chopstick [ (i+1) %5] ) ;	//拿右
		V(mutex);
		吃飯...
		V(chopstick[i] ) ;		// 放左
		V(chopstick[ (i+1) %5] ) ;	// 放右
		思考...
	}
}

更準確的說法應該是:

  • 各哲學家拿筷子這件事必須互斥的執行。這就保證了即使一個哲學家在拿筷子拿到一半時被阻塞,也不會有別的哲學家會繼續嘗試拿筷子。這樣的話,當前正在吃飯的哲學家放下筷子後,被阻塞的哲學家就可以獲得等待的筷子了

知識回顧

  • 哲學家進餐問題的關鍵在於解決進程死鎖。
  • 這些進程之間只存在互斥關係,但是與之前接觸到的互斥關係不同的是,毎個進程都需要同時持有兩個臨界資源,因此就有“死鎖”問題的隱患。

20. 管程

在這裏插入圖片描述

1. 爲什麼要引入管程

  • 信號量機制存在的問題:編寫程序困難、易出錯
  • 1973年,Brinch Hansen首次在程序設計語言(Pascal)中引入了 “管程”成分—— 一種高級同步機制
producer () (
	while(1)( 
		生產一個產品;
		P(mutex);	// 1
		P(empty);	// 2
		把產品放入緩衝區;
		V(mutex); 
		V(full);
	}
}

consumer () (
	while(1)(
		P(mutex);// 像這樣如果寫錯了P操作的順序,按1,2,3執行,就會發生死鎖
		P(full);	④
		從緩衝區取出一個產品;
		V(mutex);
		V(empty); 
		使用產品;

2. 管程的定義和基本特徵

管程是一種特殊的軟件模塊,有這些部分組成:

  1. 局部於管程的共享數據結構說明;
  2. 對該數據結構進行操作的一組過程;
  3. 對局部於管程的共享數據設置初始值的語句;
  4. 管程有一個名字。

跨考Tips: “過程”其實就是“函數”

管程的基本特徵:

  • 局部於管程的數據只能被局部於管程的過程所訪問;
  • 一個進程只有通過調用管程內的過程才能進入管程訪問共享數據;
  • 每次僅允許一個進程在管程內執行某個內部過程。

3. 拓展: 用管程解決生產者消費者問題

monitor ProducerConsumer
	condition full, empty; // 管程中設置條件變量和等待/喚醒操作,以解決同步問題
	int count=0; // 緩衝區中的產品數W
	void insert (Item item) { // 把產品item放入緩衝區 (用編譯器負賁實現各進程互斥地進入管程中的過程)
		if (count == N) 	
			wait (full);	、
		count++;
		insert_item (item);
		if (count == 1)
			signal(empty);
	}
	Item remove 0 { //從緩衝區中取出_個產品
		if (count == 0)
			wait (empty);
		count--;
		if (count == N-1)
			signal(full);
		return remove_item();
	}
end monitor;

毎次僅允許一個進程在管程內執行某個內部過程。

  • 例1:兩個生產者進程併發執行,依次調用了 insert過程…
  • 例2:兩個消費者進程先執行,生產者進程後執 行…

引入管程的冃的無非就是要更方便地實現進程互斥和同步。

  1. 需要在管程中定義共享數據(如生產者消費者問題的緩衝區)
  2. 需要在管程中定義用於訪問這些共享數據的“入口” 一一其實就是一些函數(如生產者消費者問題中,可以定義一個函數用於將產品放入緩衝區,再定義一個函數用於從緩衝區取出產品)
  3. 只有通過這些特疽的“入口”才能訪問共享數據
  4. 管程中有很多“入口”,但是每次只能開放其中一個“入口”,並且只能比一個進程或線程進入(如生產者消費者問題中,各進程需要互斥地訪問共享緩衝區。管程的這種特性即可保證一個時間段內最多隻會有一個進程在訪問緩衝區。
    注意:這神互斥轉性是由編譯器負責實現袍, 程庁員不用關心
  5. 可在管程中設置條件變量等待/喚醒操作以解決同步問題。可以讓一個進程或線程在條件變量上等待(此時,該進程應先釋放管程的使用權,也就是讓出"入門”);可以通過喚醒操作將等待在條件變量上的進程或線程喚醒。

程序員可以用某種特殊的語法定義一個管程(比如:monitor Producerconsumer… end monitor;), 之後其他程序員就可以使用這個管程提供的特定“入口“(“封裝”思想)很方便地使用實現進程同步/互斥了。

4. 拓展: Java中類似於管程的機制

Java中,如果用關鍵字 synchronized 來描述一個函數,那麼這個函數同一時間段內只能被一個線程調用

static class monitor {
	private Item buffer!] = new Item[N]; 
	private int count = 0;

	public synchronized void insert(Item item){// 每次只能有一個線程進入insert函數,如果多個線程同時調用insert函數,則後來者需要排隊等待
	...
	}
}

知識回顧

在這裏插入圖片描述

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