
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 運作速度太快了,其他媒體都趕不上。有了競争就需要有規則,就像遊戲一樣,每個遊戲都需要規則,不同規則會有不同的側重點,這個看過“最強大腦”這個節目的朋友就非常清楚,每道題都有不同的考核側重點,有些是側重空間思維、有些側重邏輯推算等等。下面我們就簡單地一一講解競争資源的遊戲規則。
first in first out(先來先服務):最先進入就緒隊列的程序,先運作,運作到完成或者阻塞時,再重新排程。一般情況下,這種排程算法會和優先級政策結合,比如每個優先級一條隊列,每條隊列中的排程都使用 fcfs。
特點:簡單、比較偏于長程序、相對于其他排程算法平均周轉時間長。
round robin(輪轉):程序按送出順序存在就緒隊列,依次輪流占用 cpu 資源,運作一段固定的時間,時間到後如果還沒執行完,就繼續進入就緒隊列隊尾,排隊等待下次執行。
特點:公平、對程序的響應時間較短。
shortest process next(最短程序優先):将預期占用運作時間最短的程序優先執行,直到運作完成或阻塞時,再重新排程。
特點:有利于短程序。
shortest remaining time(最短剩餘時間優先):新程序進來時,如果新程序的預計運作時間比目前程序的剩餘運作時間更短,就搶占目前程序,
特點:有利于短程序,和 spn 的差别在于搶占這個一點,因為搶占,是以效率會比 spn 好一些。
highest response ratio next(最高響應比優先):目前運作的程序完成或者阻塞時發生排程,每次排程前,計算所有就緒程序的響應比,響應比高的程序優先運作。
響應比公式如下所示:
特點:有利于短程序,服務時間相同的程序,先來的服務會優先執行,長程序因為在等待的過程中,優先級越來越高,是以不會一直不執行。
feedback (回報):由多個就緒隊列組成的回報機制,它有如下規則:
在同一個隊列的程序,按 fcfs 算法排程,最後一個就緒隊列按 rr 算法排程;
優先級越高的隊列,時間片越小;
程序在一個時間片内未運作完,則降到下一個隊列末尾;
隻有上級隊列無就緒程序時,才運作本級就緒隊列,本級就緒隊列無程序時,才運作下級就緒隊列,以此類推
程序執行過程如下圖所示
特點:短程序有非常大的優勢,排在前面的隊列都是時間較短的。
以上就是幾個搶占資源的排程算法的說明。
上面我們講到,程序之間是在競争資源,得到資源就運作,沒得到就等待,這個需要有狀态來維護,像很多系統一樣,需要一個狀态機。
三态圖也是描述程序狀态最簡單最基礎的圖,它包含了程序的最基本的 3 個狀态,分别是:就緒态、運作态和阻塞态。
read(就緒态):程序已得到除 cpu 以外的其他所需資源。
running(運作态):程序的指令正被執行。
blocked(阻塞态):程序正等待資源或某事件發生。
就緒态的程序在被排程的時候,進入了運作态,如果時間片運作完或者有更進階别程序搶占資源,則變成就緒态等待再次被排程;如果發生事件(比如 io 事件),則從運作态轉到阻塞态,進入阻塞态的程序隻能等待事件解除重新進入就緒态。
基于三态圖,新增了 2 個狀态,分别是:建立态和退出态。
new(建立态):程序正被建立。配置設定記憶體後将被設為就緒态。
exit(退出态):程序已正常結束或出現異常結束。回收資源。
新程序剛建立還沒有配置設定資源的時候是建立态,等到配置設定了資源,被加載後就進入就緒态。當程序運作完後,就從運作态進入退出态。
基于五态圖,新增了 2 種挂起态,分别是就緒挂起态和阻塞挂起态。
就緒挂起态:另叫外存就緒态。由于記憶體容量有限,将原位于記憶體的就緒程序轉存到外存(磁盤)上。
阻塞挂起态:另叫外存阻塞态。一樣因為記憶體容量有限,将原位于記憶體的阻塞程序轉存到外存(磁盤)上。
我們可以看出,圖中新增了解除挂起的狀态轉換過程,一般是由于挂起程序優先級比較高或者記憶體空間足夠,把位于外存(磁盤)的程序轉存到記憶體中。
程序之間其實比較獨立,比如我們在日常使用的 qq 和微信,它們運作起來的程序有什麼關系麼?其實除了互相競争資源之外,沒有任何關系。
雖然上面說的程序之間沒有關系,但是有一個特殊關系需要講,就是父子關系。
先做個試驗,驗證程序的父子關系。操作步驟:
打開 cmd 指令行程式,将目前的視窗設定為 father,在 father 視窗通過指令<code>start cmd</code>啟動另一個 cmd 指令行程式;
将新開的 cmd 指令行程式的視窗設定為 son,在 son 視窗通過指令<code>start cmd</code>啟動另一個 cmd 指令行程式;
将新開的 cmd 指令行程式的視窗設定為 grandson。
操作過程如下圖所示。
通過 processexplorer 可以很清晰看到這 3 個 cmd 程序之間的關系。(想要 processexplorer 插件可以通過百度網盤下載下傳連結:https://pan.baidu.com/s/19531gf5td_of1cwxpfr9dg 提取碼:qhc6)
我們看到 father、son、grandson 三個程序呈現出我們預料中的樹形。那麼什麼是父子程序呢?簡單的說就是在程序中建立出新的程序,這個新的程序就是子程序,一個程序可以有多個子程序,但是隻能有一個父程序。在 unix 系統中,父程序通過調用 <code>fork()</code> 建立子程序,父子程序有如下特點:
父、子程序并發執行;
父、子程序共享父程序的所有資源;
子程序複制父程序的位址空間,甚至有相同的正文段和程式計數器 pc 值;
利用寫時複制(copy on write)技術減少不必要的複制:fork 時父子共用父空間,當一方試圖修改時才複制。
這裡重點講一下copy on write,使用了這個技術,父程序建立子程序的時候不會複制所有資料到子程序,省了複制的時間以及減少了大量的記憶體。這個複制不是必要的,因為如果應用程式在程序複制之後立即加載新程式,那之前的複制工作就是浪費時間和記憶體了。
講了程序父子關系,就免不了提一下僵屍程序和孤兒程序,下面分别介紹一下。
僵屍程序:子程序退出後,父程序沒有調用 wait 或 waitpid 擷取子程序的狀态資訊,子程序的程序描述符仍儲存在系統中,這種程序叫僵屍程序。
僵屍程序的危害:僵屍程序會一直占用程序号,系統能使用的程序号又是有限的,如果有大量的僵屍程序,會因為沒有可用程序号導緻無法建立新的程序。
孤兒程序:父程序結束退出,而它的子程序還在運作,這時的子程序就叫做孤兒程序。孤兒程序就被 init 程序(程序号為 1)收養,init 程序将對孤兒程序完成狀态收集工作。
孤兒程序沒有危害,因為被 init 程序托管了,init 程序會處理孤兒程序的收集工作。
指令分為特權指令(隻能由作業系統核心使用的指令)和非特權指令(隻能由使用者程式使用的指令),因為指令有特權和非特權之分,是以 cpu 也分為 2 種執行模式:系統态(可以執行所有指令,使用所有資源以及改變 cpu 狀态)和使用者态(隻能執行非特權指令)。
cpu 的系統态和使用者态之間的切換。
當程序之間需要資料傳輸、共享資料時,程序間就需要互相通訊,通訊方式有如下幾種,這裡隻是簡單概括一下,不展開講,咱的重點在于多線程,程序咱們簡單了解一下就可以,感興趣的同學可以根據要點進行深入學習。
管道是半雙工通訊,資料是單向流動,要建立程序間互相通訊,則需要 2 個管道,這種通訊方式隻能在親戚關系的程序間使用,比如父子程序。
流管道是管道進化來的,資料不再是單向流動,可以雙向流動,但是依舊是隻能在親戚關系的程序間使用。
有名管道提供了新的功能,就是給管道設定名字,它改善了上面 2 種管道通訊方式,支援了非親戚關系的程序通訊。
信号量相當于計數器,利用它來控制多個程序通路共享資源,當一個程序a在通路共享資源時,信号量防止其他程序來通路,隻有當程序a不通路共享資源了,其他程序才能通路。
信号可以在任何時候發給某一程序,不需要知道該程序目前的狀态,如果對方程序未執行,信号會存在核心中,直到程序執行後傳遞給它;如果對方程序是阻塞,則信号會延遲傳遞,等到對方程序阻塞取消後才傳遞給它。
消息隊列是存放在核心中的連結清單,可以有多個程序對這個連結清單進行寫入和讀取,它解決了信号傳遞資訊少、管道隻能傳輸無格式位元組流和緩沖區大小受限的缺點。目前有 posix 消息隊列和 system v 消息隊列。
共享記憶體即為一段能被其他程序通路的記憶體,多個程序通路同一個記憶體,達到了通訊的效果。
套接字就是我們網絡程式設計裡面的那個套接字,可以通過網絡也可以在本機進行通信,它的好處在于可以跨主機進行通信。
總的來說,程序是程式在一個資料集上的一次執行過程,它就是程式運作起來的表現。這是我們學習多線程的開篇,希望通過這篇文章,讓大家簡單地了解程序是什麼,後面我們再來深入了解多線程。
<code>覺得文章有用幫忙轉發&點贊,多謝朋友們!</code>