程序、進程與線程(一)

關於進程與線程的概念解釋網上有很多,但總感覺講的不是很清楚。於是今天打算整理一下這個方面的知識,語言力求通俗易懂

一、程序與進程

舉個具體的場景,某一天你爸媽不在家,你必須要自己做飯、洗衣服等等。你要做飯首先你得有做飯手冊呀,這個手冊中包含了做飯所需的各種步驟(比如洗米,把米放到電飯煲中,插上電飯煲電源等等),當你看完了做飯手冊,你需要材料呀,也就是大米。有了這個大米你就可以把這個大米做成相應的米飯。從某種意義上來看,這就是一個完整的進程,其中的做飯手冊就是程序,它包含了各種指令來指導進程的進行,我們所需的大米就是用戶輸入,而煮好的米飯就是相應的輸出。自然,不同的用戶輸入就會導致不同的輸出。接下來我們來看一下專業一點的解釋

1、定義

程序:由若干條具有一定功能的指令所組成的解題順序和步驟

進程:程序的一次執行,是操作系統進行資源分配和保護的基本單位

#從理論角度看,進程是對正在執行程序活動規律的一種抽象

#從實現角度看,進程是一種數據結構,目的在於有效調度和管理進入計算機主存運行的程序

2、特點

#程序是靜態的,沒有生命週期的概念;而進程是一個動態概念,它是有着自己的生命週期的。就像前面我們所說的做飯,你從開始閱讀做飯手冊到做出香噴噴的米飯就是一個進程的完整生命週期,當飯做出來以後,這條線程也就終止了。下次你還要做飯,那就是重新開啓了另外一條線程。但是做飯手冊則不一樣,只要你不扔掉它,它就永遠都在(這裏不考慮氧化等物理因素)。

#一個程序可以對應多個線程,但是一個線程只能對應一個程序。你每天都可以用同一本做飯手冊去做飯,開啓不同的做飯進程。但是你的每一次做飯進程用的都只能是某一本具體的做飯手冊。

#進程是一個能夠獨立運行的單位,可以和其他進程併發執行。比如當你把飯放入電飯煲之後,你肯定不會在那邊傻乎乎地站着,等到飯熟了,然後再去洗衣服。正常的選擇應該是,我把飯放入電飯煲之後,就先去洗衣服,等到待着飯熟了再過來把電源拔了。在這個場景中,你就相當於是一個CPU,同時開啓了做飯和洗衣服兩個進程,這兩個進程交錯進行。你先是進行一會兒做飯進程,發現該進程需要進行一些長時間等待的操作,而你又是處於空閒狀態時。你就會選擇去洗一會兒衣服。整個過程如下:

圖中的OS調度是指操作系統的調度程序。在生活中你是可以自己去調度自己的,但是CPU不行,它是需要靠操作系統的調度程序來對它進行分配。

3、程序順序執行與併發執行

順序執行的特點:

#順序性-前一程序執行完才能執行後一程序。(你必須做完飯才能去洗衣服。)

#封閉性-程序運行時獨佔資源。(跟順序性有一定關係。當你(CPU)做飯時你就被做飯這個進程獨佔了,直到做飯進程結束才能去洗衣服)

#可再現性-在相同的執行環境和資源下,程序重複執行的結果可以重現。(如果你煮飯時用的大米量和種類等都一樣,那麼理論上只要是用同一本做飯手冊去做,你做出來的米飯也是一樣的)

併發執行的特點:

沒有順序性、封閉性和可再現性。相應的反而是間斷性、非封閉性和結果的不可再現性

前面兩個比較好理解。因爲你可以在做飯的過程中去洗衣服,你不再被某個進程獨佔,而是可以交錯地去執行各個程序。至於併發執行爲何沒有可再現性,我們可以舉一些其他的例子。前面我們所舉的兩個進程之間是沒有任何聯繫的,因此它們併發執行應該還是可以具有可再現性。但是對於兩個相互有關聯的進程,就不具備可再現性了。比如,你的銀行卡里面有一萬塊錢,某一天你想了個歪點子。你先是讓另一個人去ATM機上取錢,同時你又去櫃檯讓服務人員幫你取錢。這時兩個步驟併發進行(嚴格意義上是並行,每個人都是一個CPU,但不影響理解)。如果另一個人在服務人員查詢你銀行卡餘額之前就把錢給取走了,那麼這時你的餘額就變成了零,服務人員不會給你一萬塊,因此最終你只能得到ATM機上的一萬塊。但是如果另一個人在服務人員查詢你銀行卡的餘額之後並且在服務人員手動把你的錢清零之前取出錢,這時服務人員由於查詢到你的餘額還有一萬塊,他會取一萬塊給你,而ATM機上又取出了一萬塊,因此最終你可以拿到兩萬塊。因此這個結果是沒有可再現性的。

在計算機中的表現就是同樣併發進行的兩段程序,它們每次交錯執行的具體情況都可能會不一樣,那麼如果涉及到了共享數據,執行結果很可能就會不一樣。這也就引出了我們後面會講的線程安全問題。Java中可以用volatile關鍵字來進行共享數據的同步。比如取錢這個場景。每次服務人員在查詢用戶餘額時都要先進行更新操作,取到最新數據,這樣子就可以避免服務人員取到的數據是過期的。

二、進程的狀態

1、五種狀態

New:進程已經被創建但是還沒被執行

Ready:準備執行(在就緒隊列中)

Running:正在執行,佔用處理機

Waiting:阻塞中,等待某事件發生或I/O操作結束後才能進入就緒隊列中

Terminated:因停止或取消,被OS從執行狀態釋放

2、狀態轉換圖

3、進程調度機制

作業隊列(Job Queue):在系統中所有進程作業的集合

就緒隊列(Ready Queue):在主內存中,就緒並等待執行的所有進程的集合

設備隊列(Device Queue):等待某一I/O設備的進程隊列

進程的調度一定程度上就是進程在各個隊列之間的遷移。

三、進程的內存映像

1、PCB(程序控制塊)

A、主要作用:唯一標識一個進程、爲進程的控制提供信息(進程的調度等等)、把程序變成了進程。

B、PCB的結構(存儲的信息)

C、PCB表:OS把所有的PCB都組合在一起形成一個表結構,並把它們放在內存的某一個位置。PCB中有一個位置信息定位到該PCB所對應的進程信息所在的具體位置。如下圖

#PCB表的大小限制了操作系統中可同時存在的進程個數,稱爲系統的併發度。

#兩種組織方式:

鏈表結構:把具有相同狀態的PCB連接在一起,如下圖:

表(數組指針)結構:對具有相同狀態的PCB設置自己的索引表

 

2、其他部分

Code存放程序指令,Global存放全局變量,Stack(棧)存放局部變量(比如方法的傳入參數),Heap(堆)存放程序員自己分配的數據(典型的就是new出來的東西都放在Heap中)

 

三、進程的調度

1、長程調度:

決定那個進程被加入到就緒隊列,將進程從硬盤加入到內存,並加入就緒隊列。控制着CPU多道程序的道。

補充:什麼是多道程序?多道程序其實就是多任務執行。被加載到內存的多個進程會被交錯執行(併發執行)。也就是在用戶看來,處於就緒隊列中的進程都處於同時執行的狀態。

2、短程調度:

CPU調度,決定哪個隊列作爲下一個被CPU執行的進程,並給它分配CPU資源

3、中程調度:

把進程從內存移除並降低了多道程序度(比如原本內存的就緒隊列中有五個進程,那麼多道程序度就是5。如果移除了一個,多道程序度就降爲了4),放到硬盤中。稍後再把進程加入到內存中並重新從剛纔停止的地方開始執行。這種機制被稱爲交換。

乍一看感覺這中程調度不是沒事找事嗎,好好地幹嘛把進程從內存中移除,然後再加載。其實不然,我們先來考慮一個問題。

內存的大小時一定的,那麼它所能加載的進程數量肯定也是一定的。如果某一刻內存已被現有的進程佔滿,並且這時又有新的進程請求被加載,怎麼辦?一般來說有兩種解決方案,一種是拒絕加載。必須等到內存中的某個進程執行完了之後你才能加載進行。這樣子的話會有一個問題,如果這些就緒隊列中的進程執行時間較長,其實也不用很長,只要十來秒,待加載的進程就得等待十來秒。如果這個進程時用戶剛剛創建的,那麼他就必須得先卡住十來秒,然後程序纔會開始運行,這種用戶體驗是非常差的。因此我們來看看另一種解決方案,利用中程調度。我們先把內存中的某個進程移除到硬盤,然後加載這個新的進程,這樣子用戶就不需要等待過長的時間。這裏的話還有一個問題,我們知道從硬盤拷貝文件到內存是很耗時的一個操作。但是這裏的話我們在拷貝時是按位拷貝,繞過了文件系統,大大地提高了速度。這其實就引入了虛擬內存的概念。比如你的內存有1G,而你的硬盤有4G。這時從用戶的角度來看他會覺得他有5G的內存。

 

四、進程的控制

進程的控制動作由操作系統完成,操作系統提供相應的系統服務以備調用。

1、進程的創建

進程的創建實質上就是生成一個PCB

2、進程的終止

進程在終止之前需要先終止它的所有子進程。

 

3、阻塞過程

-停止執行

-更改PCB爲阻塞

-保留現場

-PCB插入相應的阻塞隊列

-提示重新調度

 

4、喚醒過程

-根據釋放條件尋找相應的進程

-改PCB狀態爲就緒

-將PCB插入就緒隊列

 

至此,程序和進程的概念我們已經基本弄清了。稍微做個總結。程序是存放在硬盤中的,當你啓動程序時。操作系統會在硬盤中創建一個新進程,加入作業隊列中。如果當前內存足夠,操作系統會把這個新進程加載到內存,加入到就緒隊列中,等待調度程序把CPU調度給它。當它獲得CPU時,就進入執行狀態,如果調度程序給它分配的時間片已經用完了,而它還沒有執行完,此時它就會重新被加入就緒隊列中進行等待。如果它遇到了一些I/O操作,那麼它就會進行阻塞狀態。後面等I/O操作完成後被喚醒並重新加入到就緒隊列中。如果它在調度程序分配的時間片中完成了自身所帶的所有任務,那麼它就會被終止,釋放資源。

後面的話我會繼續談談,爲何需要多進程以及線程的出現是爲了解決什麼問題。

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