目錄
文章首發:程式知多少?
@
Java 多執行緒系列文章第 1 篇
要講執行緒,一般都得講一講程式,程式是何方神聖呢?下面來簡單介紹一下。
先通過工作管理員看看 Windows 系統下的程式。
從圖片來看,每一個程式都佔有 CPU、記憶體、磁碟、網路等資源。站在作業系統的角度,程式是分配資源的基本單位,也是最小單位。
程式為什麼出現?
引入程式的目的:為了使多個程式能併發執行,以提高資源的利用率和系統的吞吐量。怎麼理解這句話呢?一個程式在執行過程中會涉及很多操作,利用 CPU 計算、通過磁碟 IO 進行資料傳輸等等,我們知道當程式在進行磁碟 IO 的時候,因為速度問題,會比較慢,所在在這個過程中 CPU 會空閒下來,這會造成資源的浪費,正因為引入程式,在 A 程式進行磁碟 IO 的時候,會讓出 CPU 給 B 程式,合理地利用了 CPU 資源,使得程式之間可以併發執行。
從 CPU 角度,執行過程是這樣子的:CPU 一直在負責執行指令,程式之間互相競爭 CPU 資源,下圖有 A 和 B 程式,在一個時間點,CPU 只執行一個程式的指令,因為 CPU 執行很快,所以在我們們看起來,像是多個程式在同時跑。這就是程式帶來的好處:提高資源利用率,併發地執行多個程式。
當然引入程式也不是有益無害,它增加了系統的時間和空間開銷。空間開銷這個好理解,程式有自己的組成部分(下面會講),這個就佔用了空間。時間開銷則是程式切換需要時間。
程式的組成
程式由 3 個部分組成,分別是程式程式碼、資料集、棧和程式控制塊(Process Control Block)。
各自的作用如下:
- 程式程式碼:描述了程式需要完成的功能。
- 資料集、棧:程式在執行時所需要的資料和工作區。
- 程式控制塊:包含程式的描述資訊和控制資訊,它是程式存在的唯一標識。
如何競爭資源(排程演算法)
程式之間需要競爭資源,一般都是競爭 CPU 資源,因為 CPU 執行速度太快了,其他介質都趕不上。有了競爭就需要有規則,就像遊戲一樣,每個遊戲都需要規則,不同規則會有不同的側重點,這個看過“最強大腦”這個節目的朋友就非常清楚,每道題都有不同的考核側重點,有些是側重空間思維、有些側重邏輯推算等等。下面我們就簡單地一一講解競爭資源的遊戲規則。
FCFS
First In First Out(先來先服務):最先進入就緒佇列的程式,先執行,執行到完成或者阻塞時,再重新排程。一般情況下,這種排程演算法會和優先順序策略結合,比如每個優先順序一條佇列,每條佇列中的排程都使用 FCFS。
特點:簡單、比較偏於長程式、相對於其他排程演算法平均週轉時間長。
RR
Round Robin(輪轉):程式按提交順序存在就緒佇列,依次輪流佔用 CPU 資源,執行一段固定的時間,時間到後如果還沒執行完,就繼續進入就緒佇列隊尾,排隊等待下次執行。
特點:公平、對程式的響應時間較短。
SPN
Shortest Job Next(最短程式優先):將預期佔用執行時間最短的程式優先執行,直到執行完成或阻塞時,再重新排程。
特點:有利於短程式。
SRT
Shortest Remaining Time(最短剩餘時間優先):新程式進來時,如果新程式的預計執行時間比當前程式的剩餘執行時間更短,就搶佔當前程式,
特點:有利於短程式,和 SPN 的差別在於搶佔這個一點,因為搶佔,所以效率會比 SPN 好一些。
HRRN
Highest Response Ratio Next(最高響應比優先):當前執行的程式完成或者阻塞時發生排程,每次排程前,計算所有就緒程式的響應比,響應比高的程式優先執行。
響應比公式如下所示:
特點:有利於短程式,服務時間相同的程式,先來的服務會優先執行,長程式因為在等待的過程中,優先順序越來越高,所以不會一直不執行。
FB
Feedback (反饋):由多個就緒佇列組成的反饋機制,它有如下規則:
- 在同一個佇列的程式,按 FCFS 演算法排程,最後一個就緒佇列按 RR 演算法排程;
- 優先順序越高的佇列,時間片越小;
- 程式在一個時間片內未執行完,則降到下一個佇列末尾;
- 只有上級佇列無就緒程式時,才執行本級就緒佇列,本級就緒佇列無程式時,才執行下級就緒佇列,以此類推
程式執行過程如下圖所示
特點:短程式有非常大的優勢,排在前面的佇列都是時間較短的。
以上就是幾個搶佔資源的排程演算法的說明。
程式狀態
上面我們講到,程式之間是在競爭資源,得到資源就執行,沒得到就等待,這個需要有狀態來維護,像很多系統一樣,需要一個狀態機。
三態圖
三態圖也是描述程式狀態最簡單最基礎的圖,它包含了程式的最基本的 3 個狀態,分別是:就緒態、執行態和阻塞態。
Read(就緒態):程式已得到除 CPU 以外的其他所需資源。
Running(執行態):程式的指令正被執行。
Blocked(阻塞態):程式正等待資源或某事件發生。
就緒態的程式在被排程的時候,進入了執行態,如果時間片執行完或者有更高階別程式搶佔資源,則變成就緒態等待再次被排程;如果發生事件(比如 IO 事件),則從執行態轉到阻塞態,進入阻塞態的程式只能等待事件解除重新進入就緒態。
五態圖
基於三態圖,新增了 2 個狀態,分別是:新建態和退出態。
New(新建態):程式正被建立。分配記憶體後將被設為就緒態。
Exit(退出態):程式已正常結束或出現異常結束。回收資源。
新程式剛建立還沒有分配資源的時候是新建態,等到分配了資源,被載入後就進入就緒態。當程式執行完後,就從執行態進入退出態。
七態圖
基於五態圖,新增了 2 種掛起態,分別是就緒掛起態和阻塞掛起態。
就緒掛起態:另叫外存就緒態。由於記憶體容量有限,將原位於記憶體的就緒程式轉存到外存(磁碟)上。
阻塞掛起態:另叫外存阻塞態。一樣因為記憶體容量有限,將原位於記憶體的阻塞程式轉存到外存(磁碟)上。
我們可以看出,圖中新增了解除掛起的狀態轉換過程,一般是由於掛起程式優先順序比較高或者記憶體空間足夠,把位於外存(磁碟)的程式轉存到記憶體中。
程式關係
程式之間其實比較獨立,比如我們在日常使用的 QQ 和微信,它們執行起來的程式有什麼關係麼?其實除了互相競爭資源之外,沒有任何關係。
父子關係
雖然上面說的程式之間沒有關係,但是有一個特殊關係需要講,就是父子關係。
先做個試驗,驗證程式的父子關係。操作步驟:
- 開啟 CMD 命令列程式,將當前的視窗設定為 Father,在 Father 視窗通過命令
start cmd
啟動另一個 CMD 命令列程式; - 將新開的 CMD 命令列程式的視窗設定為 Son,在 Son 視窗通過命令
start cmd
啟動另一個 CMD 命令列程式; - 將新開的 CMD 命令列程式的視窗設定為 Grandson。
操作過程如下圖所示。
通過 ProcessExplorer 可以很清晰看到這 3 個 CMD 程式之間的關係。(想要 ProcessExplorer 外掛可以通過百度網盤下載連結:https://pan.baidu.com/s/19531gf5tD_of1CWxpFR9Dg 提取碼:qhc6)
我們看到 Father、Son、Grandson 三個程式呈現出我們預料中的樹形。那麼什麼是父子程式呢?簡單的說就是在程式中建立出新的程式,這個新的程式就是子程式,一個程式可以有多個子程式,但是隻能有一個父程式。在 Unix 系統中,父程式通過呼叫 fork()
建立子程式,父子程式有如下特點:
- 父、子程式併發執行;
- 父、子程式共享父程式的所有資源;
- 子程式複製父程式的地址空間,甚至有相同的正文段和程式計數器 PC 值;
- 利用寫時複製(Copy On Write)技術減少不必要的複製:fork 時父子共用父空間,當一方試圖修改時才複製。
這裡重點講一下Copy On Write,使用了這個技術,父程式建立子程式的時候不會複製所有資料到子程式,省了複製的時間以及減少了大量的記憶體。這個複製不是必要的,因為如果應用程式在程式複製之後立即載入新程式,那之前的複製工作就是浪費時間和記憶體了。
講了程式父子關係,就免不了提一下殭屍程式和孤兒程式,下面分別介紹一下。
殭屍程式
殭屍程式:子程式退出後,父程式沒有呼叫 wait 或 waitpid 獲取子程式的狀態資訊,子程式的程式描述符仍儲存在系統中,這種程式叫殭屍程式。
殭屍程式的危害:殭屍程式會一直佔用程式號,系統能使用的程式號又是有限的,如果有大量的殭屍程式,會因為沒有可用程式號導致無法建立新的程式。
孤兒程式
孤兒程式:父程式結束退出,而它的子程式還在執行,這時的子程式就叫做孤兒程式。孤兒程式就被 init 程式(程式號為 1)收養,init 程式將對孤兒程式完成狀態收集工作。
孤兒程式沒有危害,因為被 init 程式託管了,init 程式會處理孤兒程式的收集工作。
執行模式
指令分為特權指令(只能由作業系統核心使用的指令)和非特權指令(只能由使用者程式使用的指令),因為指令有特權和非特權之分,所以 CPU 也分為 2 種執行模式:系統態(可以執行所有指令,使用所有資源以及改變 CPU 狀態)和使用者態(只能執行非特權指令)。
CPU 的系統態和使用者態之間的切換。
程式間通訊
當程式之間需要資料傳輸、共享資料時,程式間就需要互相通訊,通訊方式有如下幾種,這裡只是簡單概括一下,不展開講,我們的重點在於多執行緒,程式我們們簡單瞭解一下就可以,感興趣的同學可以根據要點進行深入學習。
管道(Pipe)
管道是半雙工通訊,資料是單向流動,要建立程式間互相通訊,則需要 2 個管道,這種通訊方式只能在親戚關係的程式間使用,比如父子程式。
流管道(Flow Pipe)
流管道是管道進化來的,資料不再是單向流動,可以雙向流動,但是依舊是隻能在親戚關係的程式間使用。
有名管道(Named Pipe)
有名管道提供了新的功能,就是給管道設定名字,它改善了上面 2 種管道通訊方式,支援了非親戚關係的程式通訊。
訊號量(Semophore)
訊號量相當於計數器,利用它來控制多個程式訪問共享資源,當一個程式A在訪問共享資源時,訊號量防止其他程式來訪問,只有當程式A不訪問共享資源了,其他程式才能訪問。
訊號(Signal)
訊號可以在任何時候發給某一程式,不需要知道該程式當前的狀態,如果對方程式未執行,訊號會存在核心中,直到程式執行後傳遞給它;如果對方程式是阻塞,則訊號會延遲傳遞,等到對方程式阻塞取消後才傳遞給它。
訊息佇列(Message Queue)
訊息佇列是存放在核心中的連結串列,可以有多個程式對這個連結串列進行寫入和讀取,它解決了訊號傳遞資訊少、管道只能傳輸無格式位元組流和緩衝區大小受限的缺點。目前有 POSIX 訊息佇列和 System V 訊息佇列。
共享記憶體(Shared Memory)
共享記憶體即為一段能被其他程式訪問的記憶體,多個程式訪問同一個記憶體,達到了通訊的效果。
套接字(Socket)
套接字就是我們網路程式設計裡面的那個套接字,可以通過網路也可以在本機進行通訊,它的好處在於可以跨主機進行通訊。
總結
總的來說,程式是程式在一個資料集上的一次執行過程,它就是程式執行起來的表現。這是我們學習多執行緒的開篇,希望通過這篇文章,讓大家簡單地瞭解程式是什麼,後面我們再來深入瞭解多執行緒。
推薦閱讀
公眾號後臺回覆『設計模式』可以獲取《一故事一設計模式》電子書
覺得文章有用幫忙轉發&點贊,多謝朋友們!