理解作業系統之程序和執行緒
在作業系統中,設定了程序和執行緒的概念去描述程式併發執行邏輯。本文屬於研究程序和執行緒的 入門級文章。 主要從以下五個方面介紹程序以及執行緒的相關概念。
- 程序和執行緒的定義
- 作業系統中對程序和執行緒的描述
- 程序的多層排程
- 程序/執行緒之間的同步機制
- 程序/執行緒之間的通訊機制
- 如何避免程序和執行緒之間的死鎖
一丶程序和執行緒的定義
- 程序: 程序是作業系統中定義擁有資源和排程基本單位
- 執行緒: 執行緒是作業系統中排程的基本單位,執行緒不能擁有資源,可以看成輕量級的執行緒。
二丶作業系統中對程序和執行緒的描述
1. 程序和執行緒實體描述程序和執行緒均是OS中的執行實體,都是排程和分派的基本單位。
- OS定義了PCB(Proccess control block,程序控制塊)描述程序實體
- OS定義TCP(Thread control block,執行緒控制塊)描述執行緒實體 OS在建立程序/執行緒的時候必須建立對應的PCB以及TCP。PCB和TCP中儲存的內容高低相似,本文僅描述PCB的具體內容,TCP的相關的內容可類比。 PCB的主要內容: (1) 程序識別符號 ,主要用於作業系統以及使用者定位不同的程序,是程序的唯一標識 (2) 處理機狀態 ,在發生程序切換時儲存當前處理器暫存器相關資訊。處理機狀態資訊也用於程序排程時恢復現場資訊。 (3) 程序排程資訊 ,主要儲存服務程序排程的相同統計值。比如當前程序狀態,程序優先順序,已執行CPU時間,已等待CPU時間等資訊,程序阻塞原因等資訊內容。 (4) 程序控制資訊 :主要儲存程序執行相關的資訊,比如:(1)程式和資料的記憶體地址(2) 同步和通訊機制(3)程序和執行緒執行所需要的資源清單
2. 程序和執行緒的狀態描述

- 建立狀態: 程序剛建立的時候的狀態,此時作業系統剛給執行緒分配完PCB等空間
- 就緒狀態: 程序建立完畢後,獲取了 除CPU外,需要的所有資源
- 執行狀態: 處於就緒狀態的程序獲取了CPU時間片後切換至執行狀態。當程序所獲時間片消耗完畢後,將切換至就緒狀態等待下一次時間片分配。
- 阻塞狀態: 處於執行狀態的程序,發生了某種使程序暫停執行的事件,放棄CPU的執行時間,進入阻塞狀態。比如競爭臨界資源,等待IO等事件。位於阻塞狀態的程序,獲取到等待資源後,將進入就緒狀態等待CPU分配時間片。
- **銷燬狀態:**程序完成執行邏輯後,進入銷燬狀態,回收記憶體以及分配的資源。
三丶程序的多層排程
從硬碟上的可執行檔案搖身轉為記憶體中的執行程序涉及到 如下兩層排程 (1) 作業排程: 作業排程是將硬碟上執行檔案排程到記憶體中成為程序的過程,經歷過該排程的程序處於就緒狀態等待分配CPU資源。當有多個作業請求排程時,有許多經典演算法可以採用
- 先來先服務演算法: 按照作業請求排程的先後順序執行排程
- 優先順序排程演算法:每個作業均存在優先順序,按照作業的的優先順序進行排程
- 短作業有限演算法:有限排程執行時間比較短的作業。
(2) 程序排程: 程序排程是指在就緒佇列中排隊的就緒程序獲取CPU時間片資源的過程。程序排程演算法是需要介紹的重點,從較大的方向上分,其主要包括兩類:
-
基於優先權排程的演算法,該排程演算法主要區分以下四種概念
- 靜態優先權排程:靜態優先權是指,該程序所分配的優先權在執行的過程中是不可變化的,從始至終就是初始化的大小。
- 動態優先權排程:程序排程的優先權可以依據執行時的情況動態改變。比如提高排隊時間過長的程序優先權。這樣能避免飢餓程序。
- 搶佔式排程:當前執行程序的優先權若小於排隊程序程序的優先權,當前執行程序將讓出CPU時間,退出執行。
- 非搶佔式排程:當前程序一旦獲取了CPU執行時間後,便不會因為優先權的原因讓出CPU時間。除非主動結束執行或者遇見異常情況。
-
基於時間片輪轉排程演算法基於時間片的排程演算法將就緒程序排列成一個佇列,為佇列中每個就緒程序分配指定的時間片資源。若在規定的時間片內程序未執行完畢,那麼該程序將再次加入佇列的尾部等待下一次時間片資源分配。上述只是基於時間片的排程演算法的一般思想,在實際工業場景下過於粗糙。下面介紹一種較為常用的 多級反饋佇列排程演算法 具有更大的實用價值
- 多級反饋排程演算法:
- 從圖中可知,該演算法擁有N個用於排程的就緒佇列。當程序剛進入就緒狀態時時,首先進入1級就緒佇列等待CPU分配時間片資源。若未在當前時間片資源內執行完畢,那麼進入2級就緒佇列。後續排程過程以此類推。
- 只有1級就緒佇列中沒有任何程序時,2級就緒佇列中的程序才能排程之CPU。
- 從高階就緒佇列中排程到CPU時,會獲取更多的時間片資源。 TN>T3>T2>T1.
多級反饋排程演算法,其優越性一般體現在如下三點:
- 適用於較短的互動型任務。互動型任務一般只需要較短的執行時間能在1級佇列中完成,需要極低的響應延遲
- 在多級排程的過程中,短作業最多在1-2個時間片輪轉中可以排程完成。週轉時間任然較短
- 長作業,可以輪轉到高階就緒佇列中,這樣或許更多CPU執行時間。不至於因為短作業過程,長作業分配不到CPU資源而導致飢餓。
四丶程序/執行緒之間同步機制
程序與程序之間的同步,執行緒和執行緒之間的同步基本一致。本文以執行緒和執行緒之間的同步為例子介紹同步概念。
-
執行緒同步的概念:執行緒之間並不是孤立的執行,而是有序協作的向前推進執行。
-
經典的程序同步問題:
1. 消費者與生產者問題消費者執行緒和生產者執行緒同時訪問一個總大小為N的臨界資源池。當資源池中資源數目為N時,生產者執行緒不能往其中新增資料,此時臨界資源池記為滿狀態。當資源池中資源數目為0時,消費者執行緒不能從資源池中拿去資料,此時臨界資源池記為空狀態。在這樣一個場景下,需要實現三個點: - 消費者執行緒和生產者執行緒臨界資源池的訪問是互斥的。 - 臨界資源池在滿狀態時,生產者執行緒放入資料操作必須阻塞,等待資源池非滿狀態時才能繼續放入 - 臨界資源池空狀態時,消費者執行緒取資料的操作必須阻塞,等待資源池非空狀態時才能繼續取出。 解決方法:互斥鎖以及條件變數
2. 哲學家就餐問題
。上述哲學家問題中死鎖情況存在下述解決方案 (1) 至多隻能允許最多四個哲學家同時去拿同一側的筷子 (2) 哲學家同時拿起兩隻筷子,而不一隻只拿。 可以看出上述解決方法,都是通過設定限制條件,避免死鎖情況發生。
3. 讀者-寫者問題對於一個檔案,存在多個執行緒同時讀取以及多個執行緒同時寫入。在這種條件下要求對檔案的訪問不能混亂。那麼要求讀執行緒和寫執行緒必須滿足如下要求:
- 讀執行緒和寫執行緒之間對檔案的訪問是互斥的
- 寫執行緒之間對檔案的訪問是互斥的
- 讀執行緒之間對檔案的訪問不需要互斥 解決方法: 讀寫鎖
五丶如何避免程序/執行緒之間的死鎖
本節從執行緒的角度來介紹死鎖。執行緒死鎖是執行緒同步不當導致的問題。本節將從執行緒死鎖原因,執行緒死鎖的必要條件,以及規避執行緒死鎖的三個方面來分析。 1. 執行緒死鎖產生的原因 以哲學家就餐問題,來研究執行緒死鎖原因
- 競爭共享資源 :哲學家所需要的筷子就是共享資源。倘若哲學家們存在一雙私有的筷子那麼變不存在死鎖問題。
- 程序間推進順序不合理 : 競爭共享資源並不一會導致死鎖。在哲學家就餐問題中,如果能夠避免同時拿起同一側筷子這種執行順序。那麼不會發生死鎖。儘管程序之間存在共享資源競爭,但是隻要推進順序合理便能避免死鎖。
2. 執行緒死鎖產生的必要條件死鎖發生具有四個必備條件,當能夠同時滿足這四個條件時,便有可能發生死鎖。
- 互斥條件,執行緒對資源的獲取具有排他性,在獲取資源的同時獨佔資源,不允許其他執行緒訪問共享資源
- 請求和保持條件,執行緒在獲取某個資源之後,若再次申請或許新的資源但被阻塞時,並不釋放已佔有的資源
- 不剝奪條件,執行緒獲取資源之後,不會因為其他執行緒競爭而放棄資源。只能等到使用完畢或者主動釋放
- 環路條件,當執行緒之間發生死鎖的時候,必然存在一個執行緒->資源之間的環形鏈路。比如執行緒P1等到執行緒P2佔用的某個資源,執行緒P2等待執行緒P1佔用的謀和資源
3. 避免死鎖的方法
- 預防死鎖:通過破壞死鎖產生的必要條件,在預防死鎖的發生
- 避免死鎖:在對執行緒分配資源的時候,計算該次資源分配之後執行緒是否處於安全狀態。處於安全狀態則分配資源,否則並不分配資源。避免死鎖具有代表性的演算法便是銀行家演算法。這是一種非常經典的預防死鎖的方法
- 檢測和接觸死鎖:該種方法在程序競爭資源的時候,並不任何預防或者避免死鎖的方法。它僅僅提供對死鎖的發現機制,在產生死鎖之後,通過殺死死鎖執行緒達到接觸死鎖的目的。