五分鐘掃盲:程式與執行緒基礎必知

飛天小牛肉發表於2021-02-24

 

? 盡人事,聽天命。博主東南大學碩士在讀,熱愛健身和籃球,樂於分享技術相關的所見所得,關注公眾號 @ 飛天小牛肉,第一時間獲取文章更新,成長的路上我們一起進步

? 本文已收錄於 CS-Wiki(Gitee 官方推薦專案,現已 1.0k+ star),致力打造完善的後端知識體系,在技術的路上少走彎路,歡迎各位小夥伴前來交流學習

 

全文脈絡思維導圖如下:

五分鐘掃盲:程式與執行緒基礎必知

1. 程式與執行緒的簡單解釋

程式(Process)和執行緒(Thread)是作業系統的基本概念,但是它們比較抽象,不容易掌握。以下這個解釋出自阮一峰老師的部落格(http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html),雖然不是非常嚴謹,但是足夠形象,看完之後能對程式和執行緒有個非常直觀的印象,這樣也方便理解後文。

① 計算機的核心是 CPU,它承擔了所有的計算任務。它就像一座工廠,時刻在執行。

五分鐘掃盲:程式與執行緒基礎必知

假定工廠的電力有限,一次只能供給一個車間使用。也就是說,一個車間開工的時候,其他車間都必須停工。背後的含義就是,單個 CPU 一次只能執行一個任務。

③ 程式就好比工廠的車間,它代表 CPU 所能處理的單個任務。任一時刻,CPU 總是執行一個程式,其他程式處於非執行狀態。

五分鐘掃盲:程式與執行緒基礎必知

④ 一個車間裡,可以有很多工人。他們協同完成一個任務。

五分鐘掃盲:程式與執行緒基礎必知

⑤ 執行緒就好比車間裡的工人。一個程式可以包括多個執行緒。

五分鐘掃盲:程式與執行緒基礎必知

⑥ 車間的空間是工人們共享的,比如許多房間是每個工人都可以進出的。這象徵一個程式的記憶體空間是共享的,每個執行緒都可以使用這些共享記憶體。

五分鐘掃盲:程式與執行緒基礎必知

⑦ 可是,每間房間的大小不同,有些房間最多隻能容納一個人,比如廁所。裡面有人的時候,其他人就不能進去了。這代表一個執行緒使用某些共享記憶體時,其他執行緒必須等它結束,才能使用這一塊記憶體。

五分鐘掃盲:程式與執行緒基礎必知

⑧ 一個防止他人進入的簡單方法,就是門口加一把鎖。先到的人鎖上門,後到的人看到上鎖,就在門口排隊,等鎖開啟再進去。這就叫"互斥鎖"(Mutual exclusion,縮寫 Mutex),防止多個執行緒同時讀寫某一塊記憶體區域。

⑨ 還有些房間,可以同時容納 n 個人,比如廚房。也就是說,如果人數大於 n,多出來的人只能在外面等著。這好比某些記憶體區域,只能供給固定數目的執行緒使用。

⑩ 這時的解決方法,就是在門口掛 n 把鑰匙。進去的人就取一把鑰匙,出來時再把鑰匙掛回原處。後到的人發現鑰匙架空了,就知道必須在門口排隊等著了。這種做法叫做 "訊號量"(Semaphore),用來保證多個執行緒不會互相沖突。

五分鐘掃盲:程式與執行緒基礎必知

不難看出,互斥鎖 Mutex 是訊號量 semaphore 的一種特殊情況(n = 1時)。也就是說,完全可以用後者替代前者。但是,因為 Mutex 較為簡單,且效率高,所以在必須保證資源獨佔的情況下,還是採用這種設計。

2. 程式基礎掃盲

① 什麼是程式

結合上文的簡單解釋,下面給出程式的科學定義:程式是程式在某個資料集合上的一次執行活動,也是作業系統進行資源分配和保護的基本單位

通俗來說,程式就是程式的一次執行過程,程式是靜態的,它作為系統中的一種資源是永遠存在的。而程式是動態的,它是動態的產生,變化和消亡的,擁有其自己的生命週期。

舉個例子:同時掛三個 QQ 號,它們就對應三個 QQ 程式,退出一個就會殺死一個對應的程式。但是,就算你把這三個 QQ 全都退出了,QQ 這個程式死亡了嗎?顯然沒有。

程式不僅包含正在執行的程式實體,並且包括這個執行的程式中佔據的所有系統資源,比如說 CPU、記憶體、網路資源等。很多小夥伴在回答程式的概念的時候,往往只會說它是一個執行的實體,而會忽略掉程式所佔據的資源。比如說,同樣一個程式,同一時刻被兩次執行了,那麼他們就是兩個獨立的程式。

② 程式的組成

程式主要由三個部分組成:

1)程式控制塊 PCB。包含如下幾個部分:

  • 程式描述資訊

  • 程式控制和管理資訊

  • 資源分配清單

  • CPU 相關資訊

2)資料段。即程式執行過程中各種資料(比如程式中定義的變數)

3)程式段。就是程式的程式碼(指令序列)

舉個例子:同時掛三個 QQ 號,會對應三個 QQ 程式,它們的 PCB、資料段各不相同,但程式段的內容都是相同的 (都是執行著相同的 QQ 程式)

五分鐘掃盲:程式與執行緒基礎必知

PCB 是提供給作業系統用的,而程式段、資料段是給程式自己用的。

程式控制塊 PCB

每個程式有且僅有一個程式控制塊(Process Control Block,PCB),或稱程式描述符,它是程式存在的唯一標識,是作業系統用來記錄和刻畫程式狀態及環境資訊的資料結構,也是作業系統掌握程式的唯一資料結構和管理程式的主要依據。所以說 PCB 是提供給作業系統使用的。

通俗的解釋:作業系統需要對各個程式進行管理,但凡管理時所需要的資訊,都會被放在 PCB 中,PCB 是程式存在的唯一標誌。建立程式和撤銷程式等都是指對 PCB 的操作,當程式被建立時,作業系統為其建立 PCB,當程式結束時,會回收其 PCB。

一般來說,PCB 會包含如下四類資訊:

1)程式描述資訊:用來讓作業系統區分各個程式

  • 當程式被建立時,作業系統會為該程式分配一個唯一的、不重複的 “身份證號”— PID(ProcessID,程式 ID)

  • 另外,程式描述資訊還包含程式所屬的使用者 ID(UID

2)程式控制和管理資訊:記錄程式的執行情況。比如 CPU 的使用時間、磁碟使用情況、網路流量使用情況等。

3)資源分配清單:記錄給程式分配了哪些資源。比如分配了多少記憶體、正在使用哪些 I/O 裝置、正在使用哪些檔案等。

4)CPU 相關資訊:程式在讓出 CPU 時,必須儲存該程式在 CPU 中的各種資訊,比如各種暫存器的值。用於實現程式切換,確保這個程式再次執行的時候恢復 CPU 現場,從斷點處繼續執行。這就是所謂的儲存現場資訊

 

五分鐘掃盲:程式與執行緒基礎必知

③ 程式的狀態

儘管每一個程式都是獨立的實體,有其自己的 PCB 和內部狀態,但是程式之間經常需要相互作用。一個程式的輸出結果可能是另一個程式的輸入。假設程式 A 的輸入依賴程式 B 的輸出,那麼在程式 B 的輸出結果沒有出來之前,程式 A 就無法執行,它就會被阻塞。這就是程式的阻塞態。

經典的程式三態模型如下:

  • 執行態(running):程式佔有 CPU 正在執行。

  • 就緒態(ready):程式具備執行條件,等待系統分配 CPU 以便執行。

  • 阻塞態 / 等待態(wait):程式不具備執行條件,正在等待某個事件的完成。

五分鐘掃盲:程式與執行緒基礎必知

上圖中的時間片用完,可以這樣理解:

程式是併發執行的嘛,巨集觀上在一段時間內能同時執行多個程式,但其實微觀上是交替發生的。也就是說 CPU 一般不會讓一個程式一次性執行完,為了保證所有程式可以得到公平排程,CPU 時間被劃分為一段段的時間片,這些時間片再被輪流分配給各個程式。某個程式的時間片用完後這個程式就會進入就緒態,而其他被分配到時間片的程式就會進入執行態。這個處於就緒態的程式就需要等待程式排程程式的下一次排程,為其分配 CPU 時間片後才能再次恢復執行。

需要注意的是:阻塞態是由於缺少需要的資源從而由執行態轉換而來,但是該資源不包括 CPU 時間片,缺少 CPU 時間片會從執行態轉換為就緒態

很多系統中都增加了新建態(new)和終止態(exit),形成五態模型

  • 新建態(new):程式正在被建立時的狀態

  • 終止態(exit):程式正在從系統中消失時的狀態

五分鐘掃盲:程式與執行緒基礎必知

從上圖可以發現,只有就緒態和執行態可以相互轉換,其它的都是單向轉換

這些不同狀態的程式作業系統是如何進行管理的呢?上文說過,PCB 是提供給作業系統使用的,是作業系統管理程式的主要依據。沒錯,操作就是通過 PCB 來管理這些擁有不同狀態的程式的。

程式的 PCB 會通過某種方式組織起來,一般來說,作業系統會把處於同一狀態的所有程式的 PCB 連結在一起,這種資料結構就稱為程式佇列(Process Queue)。

④ 程式控制

所謂程式控制就是對系統中的所有程式實施有效的管理,實現程式狀態轉換功能。包括建立程式、阻塞程式、喚醒程式、終止程式等,這些功能均由原語來實現,作業系統通過原語來完成程式原理,包括程式的同步和互斥、程式的通訊和管理。

什麼是原語?原語是一種特殊的程式,它的執行具有原子性。 也就是說,這段程式的執行必須一氣呵成,不可中斷。原語是作業系統核心裡的一段程式:

五分鐘掃盲:程式與執行緒基礎必知

思考一下:為什麼程式控制(程式狀態轉換)的過程要一氣呵成,不可中斷?

答:如果程式狀態轉換的過程不能一氣呵成,就有可能導致作業系統中的某些關鍵資料結構資訊不統一,這會影響作業系統進行別的管理工作。

程式的建立

作業系統初始啟動時會建立承擔系統資源分配和控制管理的一些系統程式,同時還會建立一個所有使用者程式的祖先,其他使用者程式是在應用程式執行時建立的。

作業系統允許一個程式建立另一個程式,而且允許子程式繼承父程式所擁有的資源,當子程式被終止時,其在父程式處繼承的資源應當還給父程式。同時,終止父程式時同時也會終止其所有的子程式。

建立程式的過程,也就是建立原語包含的內容如下:

  • 在程式列表中增加一項,從 PCB 池中申請一個空閒的 PCB(PCB 是有限的,若申請失敗則建立失敗),為新程式分配一個唯一的程式識別符號;

  • 為新程式分配地址空間,由程式管理程式確定載入至程式地址空間中的程式;

  • 為新程式分配各種資源;

  • 初始化 PCB,如程式識別符號、CPU 初始狀態等;

  • 把新程式的狀態設定為就緒態,並將其移入就緒佇列,等待被排程執行。

什麼事件會觸發程式的建立呢?有如下四種情況:

  • 使用者登入:分時系統中,使用者登入成功,系統會為其建立一個新的程式

  • 作業排程:多道批處理系統中,有新的作業放入記憶體中,會為其建立一個新的程式

  • 提供服務:使用者向作業系統提出某些請求時,會新建一個程式處理該請求

  • 應用請求:由使用者程式主動請求建立一個子程式

程式的終止

程式的終止也稱為撤銷,程式完成特定工作或出現嚴重錯誤後必須被終止。引起程式終止的事件有三種:

  • 正常結束:程式自己請求終止(exit 系統呼叫)

  • 異常結束:比如整數除 0,非法使用特權指令,然後被作業系統強行終止

  • 外界干預:Ctrl + Alt + delete 開啟程式管理器,使用者手動殺死程式

終止(撤銷)程式的過程,也就是撤銷原語包含的內容如下:

  • 從 PCB 集合中找到終止程式的 PCB;

  • 若程式處於執行態,則立即剝奪其 CPU,終止該程式的執行,然後將 CPU 資源分配給其他程式;

  • 如果其還有子程式,則應將其所有子程式終止;

  • 將該程式所擁有的全部資源都歸還給父程式或作業系統;

  • 回收 PCB 並將其歸還至 PCB 池。

程式的阻塞和喚醒

程式阻塞是指程式讓出 CPU 資源轉而等待一個事件,如等待資源、等待 I/O 操作完成等。程式通常使用阻塞原語來阻塞自己,所以阻塞是程式的自主行為,是一個同步事件。當等待事件完成時會產生一箇中斷,啟用作業系統,在系統的控制下將被阻塞的程式喚醒,也就是喚醒原語。

程式的阻塞和喚醒顯然是由程式切換來完成的。

程式的阻塞步驟,也就是阻塞原語的內容為:

  • 找到將要被阻塞的程式對應的 PCB;

  • 保護程式執行現場,將 PCB 狀態資訊設定為阻塞態,暫時停止程式執行;

  • 將該 PCB 插入相應事件的阻塞佇列(等待佇列)。

程式的喚醒步驟,也就是喚醒原語的內容為:

  • 在該事件的阻塞佇列中找到相應程式的 PCB;

  • 將該 PCB 從阻塞佇列中移出,並將程式的狀態設定為就緒態;

  • 把該 PCB 插入到就緒佇列中,等待被排程程式排程。

阻塞原語和喚醒原語的作用正好相反,阻塞原語使得程式從執行態轉為阻塞態,而喚醒原語使得程式從阻塞態轉為就緒態。如果某個程式使用阻塞原語來阻塞自己,那麼他就必須使用喚醒原語來喚醒自己,因何事阻塞,就由何事喚醒,否則被阻塞的程式將永遠處於阻塞態。因此,阻塞原語和喚醒原語是成對出現的

⑤ 程式上下文切換

所謂程式的上下文切換,就是說各個程式之間是共享 CPU 資源的,不可能一個程式永遠佔用著 CPU 資源,不同的時候程式之間需要切換,使得不同的程式被分配 CPU 資源,這個過程就是程式的上下文切換,一個程式切換到另一個程式執行

因為程式是由核心進行管理和排程的,所以程式的上下文切換一定發生在核心態

程式上下文的切換也是一個原語操作,稱為切換原語,其內容如下:

  • 首先,將程式 A 的執行環境資訊存入 PCB,這個執行環境資訊就是程式的上下文(Context)

  • 然後,將 PCB 移入相應的程式佇列;

  • 選擇另一個程式 B 進行執行,並更新其 PCB 中的狀態為執行態

  • 當程式 A 被恢復執行的時候,根據它的 PCB 恢復程式 A 所需的執行環境

引起程式上下文切換的事件,也就是某個佔用 CPU 資源執行的當前程式被趕出 CPU 的原因有如下:

  • 當前程式的時間片到

  • 有更高優先順序的程式到達

  • 當前程式主動阻塞

  • 當前程式終止

3. 執行緒基礎掃盲

① 什麼是執行緒

結合文章開頭的簡單解釋,一個程式中可以有多個執行緒,它們共享這個程式的資源。

舉個例子,QQ 和 Chrome 瀏覽器是兩個程式,Chrome 程式裡面有很多執行緒,例如 HTTP 請求執行緒、事件響應執行緒、渲染執行緒等等,執行緒的併發執行使得在瀏覽器中點選一個新連結從而發起 HTTP 請求時,瀏覽器還可以響應使用者的其它事件。

② 為什麼要引入執行緒

早期的作業系統都是以程式作為獨立執行的基本單位的,直到後期電腦科學家們又提出了更小的能獨立執行的基本單位,也就是執行緒。這就好比物理學家研究物質組成一樣:先發現了分子,然後繼續細分發現原子,再後來是原子核和電子、夸克等等。

那麼,為什麼要引入執行緒呢?我們只需要記住這句話:執行緒又稱為迷你程式,但是它比程式更容易建立,也更容易撤銷

從上文我們知道,程式是擁有資源的基本單位,而且還能夠進行獨立排程,這就猶如一個隨時揹著糧草的士兵,這必然會造成士兵的執行命令(戰鬥)的速度。所以,一個簡單想法就是:分配兩個士兵執行同一個命令:一個負責攜帶所需糧草隨時供給,另一個士兵負責執行命令(戰鬥)。這就是執行緒的思想,輕裝上陣的士兵就是執行緒

五分鐘掃盲:程式與執行緒基礎必知

用嚴謹的語言描述來說就是:由於建立或撤銷程式時,系統都要為之分配或回收資源,如記憶體空間、I/O 裝置等,需要較大的時空開銷,限制了併發程度的進一步提高。為減少程式切換的開銷,把程式作為資源分配單位和排程單位這兩個屬性分開處理,即程式還是作為資源分配的基本單位,但是不作為排程的基本單位(很少排程或切換),把排程執行與切換的責任交給執行緒,即執行緒成為獨立排程的基本單位,它比程式更容易(更快)建立,也更容易撤銷。

記住這句話!引入執行緒前,程式是資源分配和獨立排程的基本單位。引入執行緒後,程式是資源分配的基本單位,執行緒是獨立排程的基本單位

③ 執行緒優缺點

執行緒的特徵和程式差不多,程式有的他基本都有,比如:

  • 執行緒具有就緒、阻塞、執行三種基本狀態,同樣具有狀態之間的轉換關係;

  • 執行緒間可以併發執行

  • 在多 CPU 環境下,各個執行緒也可以分派到不同的 CPU 上並行執行

執行緒的優點:

  • 一個程式中可以同時存在多個執行緒,這些執行緒共享該程式的資源。程式間的通訊必須請求作業系統服務(因為 CPU 要切換到核心態),開銷很大。而同程式下的執行緒間通訊,無需作業系統干預,開銷更小。

    不過,需要注意的是:從屬於不同程式的執行緒間通訊,也必須請求作業系統服務。

  • 執行緒間的併發比程式的開銷更小,系統併發性提升。

    同樣,需要注意的是:從屬於不同程式的執行緒間切換,它是會導致程式切換的,所以開銷也大。

執行緒的缺點:

  • 當程式中的一個執行緒奔潰時,會導致其所屬程式的所有執行緒奔潰。

舉個例子,對於遊戲的使用者設計,就不應該使用多執行緒的方式,否則一個使用者掛了,會影響其他同個程式的執行緒。

4. 總結

作業系統的設計,從程式和執行緒的角度來說,可以歸結為三點:

  • 以多程式形式,允許多個任務同時執行;

  • 以多執行緒形式,允許單個任務分成不同的部分執行;

  • 提供協調機制,一方面防止程式之間和執行緒之間產生衝突,另一方面允許程式之間和執行緒之間共享資源。

參考資料

? 關注公眾號 | 飛天小牛肉,即時獲取更新

  • 博主東南大學碩士在讀,利用課餘時間運營一個公眾號『 飛天小牛肉 』,2020/12/29 日開通,專注分享計算機基礎(資料結構 + 演算法 + 計算機網路 + 資料庫 + 作業系統 + Linux)、Java 基礎和麵試指南的相關原創技術好文。本公眾號的目的就是讓大家可以快速掌握重點知識,有的放矢。希望大家多多支援哦,和小牛肉一起成長 ?

  • 並推薦個人維護的開源教程類專案: CS-Wiki(Gitee 推薦專案,現已 1.0k+ star), 致力打造完善的後端知識體系,在技術的路上少走彎路,歡迎各位小夥伴前來交流學習 ~ ?

相關文章