在作業系統中,設定了程式和執行緒的概念去描述程式併發執行邏輯。本文屬於研究程式和執行緒的入門級文章。 主要從以下五個方面介紹程式以及執行緒的相關概念。
- 程式和執行緒的定義
- 作業系統中對程式和執行緒的描述
- 程式的多層排程
- 程式/執行緒之間的同步機制
- 程式/執行緒之間的通訊機制
- 如何避免程式和執行緒之間的死鎖
一丶程式和執行緒的定義
- 程式: 程式是作業系統中定義擁有資源和排程基本單位
- 執行緒: 執行緒是作業系統中排程的基本單位,執行緒不能擁有資源,可以看成輕量級的執行緒。
二丶作業系統中對程式和執行緒的描述
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. 避免死鎖的方法
- 預防死鎖:通過破壞死鎖產生的必要條件,在預防死鎖的發生
- 避免死鎖:在對執行緒分配資源的時候,計算該次資源分配之後執行緒是否處於安全狀態。處於安全狀態則分配資源,否則並不分配資源。避免死鎖具有代表性的演算法便是銀行家演算法。這是一種非常經典的預防死鎖的方法
- 檢測和接觸死鎖:該種方法在程式競爭資源的時候,並不任何預防或者避免死鎖的方法。它僅僅提供對死鎖的發現機制,在產生死鎖之後,通過殺死死鎖執行緒達到接觸死鎖的目的。
六丶程式/執行緒之間通訊機制
程式/執行緒之間的同步其實是一種通訊機制,但是同步機制只是一種小規模的資料通訊。此處介紹的通訊機制是應對較大規模的資料傳輸。此處以程式之間的通訊機制為例介紹
- 共享儲存系統 共享儲存系統比較容易理解,就是多個程式擁有共同儲存空間,通過修改/讀取同一塊區域達到通訊目的。
- 訊息傳遞系統 訊息傳遞系統是指程式之間通過格式化資料包文交換資訊,最容易理解的便是計算機網路資料包文交換。位於不同計算機上應用的通訊也是程式通訊的一種場景
- 管道通訊 所謂"管道"是指用於連線一個讀程式和一個寫程式以實現他們之間的通訊的一個檔案。向管道(共享檔案)輸入的傳送程式,以字元流的形式輸入大量資料到管道中,從管道接收輸出的接收程式,將讀取大量資料。