如下文章來源於程序喵大人 ,做者程序喵大人面試
下面隆重推出我嘔心瀝血,耗時半個月完成的精心力做:算法
什麼是進程?編程
標準定義:進程是一個具備必定獨立功能的程序在一個數據集合上依次動態執行的過程。進程是一個正在執行程序的實例,包括程序計數器、寄存器和程序變量的當前值。瀏覽器
簡單來講進程就是一個程序的執行流程,內部保存程序運行所需的資源。安全
在操做系統中能夠有多個進程在運行,可對於CPU來講,同一時刻,一個CPU只能運行一個進程,但在某一時間段內,CPU將這一時間段拆分紅更短的時間片,CPU不停的在各個進程間遊走,這就給人一種並行的錯覺,像CPU能夠同時運行多個進程同樣,這就是僞並行。微信
進程和程序有什麼聯繫?網絡
一個進程是某種類型的一個活動,它有程序、輸入、輸出以及狀態。單個處理器能夠被若干進程共享,它使用某種調度算法決定什麼時候中止一個進程的工做,並轉而爲另外一個進程提供服務。數據結構
程序是產生進程的基礎多線程
程序的每次運行產生不一樣的進程併發
進程是程序功能的體現
經過屢次執行,一個程序可對應多個進程;經過調用關係,一個進程可包括多個程序
進程和程序有什麼區別?
進程是動態的,程序是靜態的:程序是有序代碼的集合,進程是程序的執行。
進程是暫時的,程序是永久的:進程是一個狀態變化的過程,程序可長久保存。
進程和程序的組成不一樣:進程的組成包括程序、數據和進程控制塊(進程狀態信息)。
進程有什麼特色?
動態性:可動態的建立和結束進程
併發性:能夠被獨立的調度並佔用處理機併發運行
獨立性:不一樣進程的工做不相互影響
制約性:因訪問共享資源或進程間同步而產生制約
進程如何建立?
有什麼事件會觸發進程的建立呢?
系統初始化:當啓動操做系統時,一般會建立不少進程,有些是同用戶交互並替他們完成工做的前臺進程,其它的都是後臺進程,後臺進程和特定用戶沒有關係,但也提供某些專門的功能,例如接收郵件等,這種功能的進程也稱爲守護進程。計劃任務是個典型的守護進程,它每分鐘運行一次來檢查是否有工做須要它完成。若是有工做要作,它就會完成此工做,而後進入休眠狀態,直到下一次檢查時刻的到來。
正在運行的程序執行了建立進程的系統調用:在一個進程中又建立了一個新的進程,這種狀況很常見。
用戶請求建立一個新進程:這種狀況相信每一個人都見過,用電腦時雙擊某個應用圖標,就會有至少一個進程被建立。
一個批處理做業的初始化:這種情形不常見,僅在大型機的批處理系統中應用,用戶在這種系統中提交批處理做業,在操做系統認爲有資源可運行另外一個做業時,它建立一個新的進程,並運行其輸入隊列中的下一個做業。
歸根到底:在UNIX系統中,只有fork系統調用才能夠建立新進程,使用方式以下:
#include<stdio.h>#include<unistd.h>intmain(){pid_tid = fork();if(id <0) { perror("fork\n"); }elseif(id ==0) {// 子進程printf("子進程\n"); }else{// 父進程printf("父進程\n"); }return0;}
進程建立以後,父子進程都有各自不一樣的地址空間,其中一個進程在其地址空間的修改對另外一個進程不可見。子進程的初始化空間是父進程的一個副本,這裏涉及兩個不一樣地址空間,不可寫的內存區是共享的,某些UNIX的實現使程序正文在二者間共享,由於它是不可修改的。
還有一種寫時複製共享技術,子進程共享父進程的全部內存,一旦二者之一想要修改部份內存,則這塊內存被複制確保修改發生在當前進程的私有內存區域。
進程爲什麼終止?
有什麼事件會觸發進程的終止呢?
正常退出(自願):進程完成了工做正常終止,UNIX中退出進程的系統調用是exit。
出錯退出(自願):進程發現了錯誤而退出。能夠看以下代碼:
#include<stdio.h>#include<stdlib.h>voidFunc(){if(error) {// 有錯誤就退出程序exit(1); }}intmain(){ Func();}
嚴重錯誤(非自願):進程發生了嚴重的錯誤而不得不退出,一般是程序的錯誤致使,例如執行了一條非法指令,引用不存在的內存,或者除數是0等,出現這些錯誤時進程默認會退出。而有些時候若是用戶想自行處理某種類型的錯誤,發生不一樣類型錯誤時進程會收到不一樣類型的信號,用戶註冊處理不一樣信號的函數便可。
被其它進程殺死(非自願):其它進程執行kill系統調用通知操做系統殺死某個進程。
操做系統如何進行進程管理?
這裏就不得不提到一個數據結構:進程控制塊(PCB),操做系統爲每一個進程都維護一個PCB,用來保存與該進程有關的各類狀態信息。進程能夠抽象理解爲就是一個PCB,PCB是進程存在的惟一標誌,操做系統用PCB來描述進程的基本狀況以及運行變化的過程,進程的任何狀態變化都會經過PCB來體現。
PCB包含進程狀態的重要信息,包括程序計數器、堆棧指針、內存分配情況、所打開文件的狀態、帳號和調度信息,以及其它在進程由運行態轉換到就緒態或阻塞態時必須保存的信息,從而保證該進程隨後能再次啓動,就像從未中斷過同樣。後一小節會具體介紹PCB。
提到進程管理,有一個概念咱們必需要知道,就是中斷向量,中斷向量是指中斷服務程序的入口地址。一個進程在執行過程當中可能會被中斷無數次,可是每次中斷後,被中斷的進程都要返回到與中斷髮生前徹底相同的狀態。
中斷髮生後操做系統最底層作了什麼呢?
1)硬件壓入堆棧程序計數器等;
2)硬件從中斷向量裝入新的程序計數器;
3)彙編語言過程保存寄存器值;
4)彙編語言過程設置新的堆棧;
5)C中斷服務例程運行(典型的讀和緩衝輸入);
6)調度程序決定下一個將運行的進程;
7)C過程返回到彙編代碼;
8)彙編語言過程開始運行新的當前進程。
進程控制塊中存儲了什麼信息?
進程標識信息:如本進程的標識,本進程的父進程標識,用戶標識等。
處理機狀態信息保護區:用於保存進程的運行現場信息:
用戶可見寄存器:用戶程序可使用的數據,地址等寄存器
控制和狀態寄存器:程序計數器,程序狀態字
棧指針:過程調用、系統調用、中斷處理和返回時須要用到它
進程控制信息:
調度和狀態信息:用於操做系統調度進程使用
進程間通訊信息:爲支持進程間與通訊相關的各類標識、信號、信件等,這些信息存在接收方的進程控制塊中
存儲管理信息:包含有指向本進程映像存儲空間的數據結構
進程所用資源:說明由進程打開使用的系統資源,如打開的文件等
有關數據結構鏈接信息:進程能夠鏈接到一個進程隊列中,或鏈接到相關的其餘進程的PCB
進程如何進行生命週期管理?
進程建立:
建立進程有三個主要事件:
系統初始化
用戶請求建立一個新進程
一個正在運行的進程執行建立進程的系統調用
進程運行:內核選擇一個就緒的進程,讓它佔用處理機並運行,這裏就涉及到了進程的調度策略,選擇哪一個進程調度?爲何選擇調度這個進程呢?(莫慌,下面會介紹哈)
進程等待:
在如下狀況下進程會等待(阻塞):
請求並等待系統服務,沒法立刻完成
啓動某種操做,沒法立刻完成
須要的數據沒有到達。
注意:進程只能本身阻塞本身,由於只有進程自身才能知道什麼時候須要等待某種事件的發生。
進程喚醒:
進程只能被別的進程或操做系統喚醒,喚醒進程的緣由有:
被阻塞進程須要的資源可被知足
被阻塞進程等待的事件到達
將該進程的PCB插入到就緒隊列
進程結束:
在如下四種狀況下進程會結束:
自願型正常退出
自願型錯誤退出
強制型致命錯誤退出
強制型被其它進程殺死退出
進程都有什麼狀態?
不一樣系統設置的進程狀態是不一樣的,多數系統中的進程在生命結束前有三種基本狀態,進程只會處於三種基本狀態之一:
運行狀態:進程正在處理機上運行時就處在運行狀態,該時刻進程時鐘佔用着CPU;
就緒狀態:萬事俱備,只欠東風,進程已經得到了除處理機以外的一切所需資源,一旦獲得處理機就能夠運行;就緒態中的進程其實能夠運行,但由於其它進程正在佔用着CPU而暫時中止運行;
等待狀態(阻塞狀態):進程正在等待某一事件而暫停運行,等待某個資源或者等待輸入輸出完成。除非某種外部事件發生,不然阻塞態的進程不能運行;
進程狀態變化圖以下:
在操做系統發現進程不能繼續運行下去時,進程由於等待輸入而被阻塞,進程從運行態轉換到阻塞態!調度程序選擇了另外一個進程執行時,當前程序就會從運行態轉換到就緒態!被調度程序選擇的程序會從就緒態轉換到運行態!當阻塞態的進程等待的一個外部事件發生時,就會從阻塞態轉換到就緒態,此時若是沒有其餘進程運行時,則馬上從就緒態轉換到運行態!
某些系統設置下進程還會有其它狀態:
建立狀態:進程正在被建立還沒被轉到就緒狀態以前的狀態;
結束狀態:進程正在從系統中消失時的狀態。
有些與進程管理相關的系統調用讀者有必要了解一下:
什麼是進程掛起?爲何會出現進程掛起?
進程掛起就是爲了合理且充分的利用系統資源,把一個進程從內存轉到外存。進程在掛起狀態時,意味着進程沒有佔用內存空間,處在掛起狀態的進程映射在磁盤上。進程掛起一般有兩種狀態:
阻塞掛起狀態:進程在外存並等待某事件的出現;
就緒掛起狀態:進程在外存,但只要進入內存便可運行。
有什麼與進程掛起相關的狀態轉換?
進程掛起可能有如下幾種狀況:
阻塞到阻塞掛起:沒有進程處於就緒狀態或就緒進程要求更多內存資源時,會進行這種轉換,以提交新進程或運行就緒進程;
就緒到就緒掛起:當有高優先級阻塞進程或低優先級就緒進程時,系統會選擇掛起低優先級就緒進程;
運行到就緒掛起:對於搶佔式分時系統,當有高優先級阻塞掛起進程因事件出現而進入就緒掛起時,系統可能會把運行進程轉到就緒掛起狀態;
阻塞掛起到就緒掛起:當有阻塞掛起進程有相關事件出現時,系統會把阻塞掛起進程轉換爲就緒掛起進程。
有進程掛起那就有進程解掛:指一個進程從外存轉到內存,相關狀態有:
就緒掛起到就緒:沒有就緒進程或就緒掛起進程優先級高於就緒進程時,就會進行這種轉換;
阻塞掛起到阻塞:當一個進程釋放足夠內存時,系統會把一個高優先級阻塞掛起進程轉換爲阻塞進程。
什麼是進程調度?操做系統對於進程調度都有什麼策略?
當系統中有多個進程同時競爭CPU,若是隻有一個CPU可用,那同一時刻只會有一個進程處於運行狀態,操做系統必需要選擇下一個要運行的是哪一個進程,在操做系統中,完成選擇工做的這部分稱爲調度程序,該程序使用的算法稱做調度算法。
何時進行調度?
系統調用建立一個新進程後,須要決定是運行父進程仍是運行子進程
一個進程退出時須要作出調度決策,須要決定下一個運行的是哪一個進程
當一個進程阻塞在I/O和信號量或者因爲其它緣由阻塞時,必須選擇另外一個進程運行
當一個I/O中斷髮生時,若是中斷來自IO設備,而該設備如今完成了工做,某些被阻塞的等待該IO的進程就成爲可運行的就緒進程了,是否讓新就緒的進程運行,或者讓中斷髮生時運行的進程繼續運行,或者讓某個其它進程運行,這就取決於調度程序的抉擇了。
調度算法能夠分類:
非搶佔式調度算法:挑選一個進程,而後讓該進程運行直至被阻塞,或者直到該進程自動釋放CPU,即便該進程運行了若干個小時,它也不會被強迫掛起。這樣作的結果是,在時鐘中斷髮生時不會進行調度,在處理完時鐘中斷後,若是沒有更高優先級的進程等待,則被中斷的進程會繼續執行。簡單來講,調度程序必須等待事件結束。
非搶佔方式引發進程調度的條件:
進程執行結束,或發生某個事件而不能繼續執行
正在運行的進程因有I/O請求而暫停執行
進程通訊或同步過程當中執行了某些原語操做(wait、block等)
搶佔式調度算法:挑選一個進程,而且讓該進程運行某個固定時段的最大值。若是在該時段結束時,該進程仍在運行,它就被掛起,而調度程序挑選另外一個進程運行,進行搶佔式調度處理,須要在時間間隔的末端發生時鐘中斷,以便CPU控制返回給調度程序,若是沒有可用的時鐘,那麼非搶佔式調度就是惟一的選擇。簡單來講,就是當前運行的進程在事件沒結束時就能夠被換出,防止單一進程長時間獨佔CPU資源。下面會介紹不少搶佔式調度算法:優先級算法、短做業優先算法、輪轉算法等。
調度策略:不一樣系統環境下有不一樣的調度策略算法。調度算法也是有KPI的,對調度算法首先提的需求就是:
公平:調度算法須要給每一個進程公平的CPU份額,類似的進程應該獲得類似的服務,對一個進程給予較其它等價的進程更多的CPU時間是不公平的,被普通水平的應屆生工資倒掛也是不公平的!
執行力:每個策略必須強制執行,須要保證規定的策略必定要被執行。
平衡:須要保證系統的全部部分儘量都忙碌
可是由於不一樣的應用有不一樣的目標,不一樣的系統中,調度程序的優化也是不一樣的,大致能夠分爲三種環境:
批處理系統
批處理系統的管理者爲了掌握系統的工做狀態,主要關注三個指標:
吞吐量:是系統每小時完成的做業數量
週轉時間:指從一個做業提交到完成的平均時間
CPU利用率:儘量讓CPU忙碌,但又不能過量
調度算法:
先來先服務
先來後到嘛,就像平時去商店買東西須要排隊同樣,使用該算法,進程按照它們請求CPU的順序來使用CPU,該算法最大的優勢就是簡單易於實現,太容易的不必定是好的,該算法也有很大的缺點:平均等待時間波動較大,時間短的任務可能排隊排在了時間長的任務後面。舉個生活中的例子,排着隊去取快遞,若是每一個人都很快取出來快遞還好,若是前面有幾我的磨磨唧唧到快遞櫃前纔拿出手機打開app,再找半分鐘它的取件碼,就會嚴重拖慢後面的人取快遞的速度,同理排着隊的進程若是每一個進程都很快就運行完還好,若是其中有一個獲得了CPU的進程運行時候磨磨唧唧很長時間都運行不完,那後面的進程基本上就沒有機會運行了!
最短做業優先
該調度算法是非搶佔式的算法,每一個進程執行期間不會被打斷,每次都選擇執行時間最短的進程來調度,但問題來了,操做系統怎麼可能知道進程具體的執行時間呢,因此該算法註定是基於預測性質的理想化算法,並且有違公平性,並且可能致使運行時間長的任務得不到調度。
最短剩餘時間優先
該調度算法是搶佔式的算法,是最短做業優先的搶佔版本,在進程運行期間,若是來了個更短期的進程,那就轉而去把CPU時間調度給這個更短期的進程,它的缺點和最短做業優先算法相似。
交互式系統
對於交互系統最重要的指標就是響應時間和均衡性啦:
響應時間:一個請求被提交到產生第一次響應所花費的時間。你給別人發微信別人看後不回覆你或者幾個小時後纔回復你,你是什麼感覺,這仍是交互式嗎?
均衡性:減小平均響應時間的波動。須要符合固有指望和預期,你給別人發微信,他有時候秒回覆,有時候幾個小時後纔回復。在交互式系統中,可預測性比高差別低平均更重要。
調度算法:
輪轉調度
每一個進程被分配一個時間段,稱爲時間片,即CPU作到雨露均沾,輪流翻各個進程的牌子,這段時間寵幸進程A,下一段時間寵幸進程B,再下一段時間寵幸進程C,確保每一個進程均可以得到CPU時間,若是CPU時間特別短的話,在外部看來像是同時寵幸了全部進程同樣。那麼問題來了,這個時間片究竟多長時間好呢?若是時間片設的過短會致使過多的進程切換,頻繁的上下文切換會下降CPU效率,而若是時間片設的太長又可能對短的交互請求的響應時間變長,一般將時間片設爲20-50ms是個比較合理的折中,大佬們的經驗規則時維持上下文切換的開銷處於1%之內。
優先級調度
上面的輪轉調度算法是默認每一個進程都同等重要,都有相同優先級,然而有時候進程須要設置優先級,例如某些播放視頻的前臺進程能夠優先於某些收發郵件的後臺守護進程被調度,在優先級調度算法中,每一個優先級都有相應的隊列,隊列裏面裝着對應優先級的進程,首先在高優先級隊列中進行輪轉調度,當高優先級隊列爲空時,轉而去低優先級隊列中進行輪轉調度,若是高優先級隊列始終不爲空,那麼低優先級的進程極可能就會飢餓到好久不能被調度。
多級隊列
多級隊列算法與優先級調度算法不一樣,優先級算法中每一個進程分配的是相同的時間片,而在多級隊列算法中,不一樣隊列中的進程分配給不一樣的時間片,當一個進程用完分配的時間片後就移動到下一個隊列中,這樣能夠更好的避免上下文頻繁切換。舉例:有一個進程須要100個時間片,若是每次調度都給分配一個時間片,則須要100次上下文切換,這樣CPU運行效率較低,經過多級隊列算法,能夠考慮最開始給這個進程分配1個時間片,而後被換出,下次分給它2個時間片,再換出,以後分給它四、八、1六、64個時間片,這樣分配的話,該進程只須要7次交換就能夠運行完成,相比100次上下文切換運行效率高了很多,但顧此就會失彼,那些須要交互的進程獲得響應的速度就會降低。
最短進程優先
交互式系統中應用最短進程優先算法實際上是很是適合的,每次都選擇執行時間最短的進程進行調度,這樣可使任務的響應時間最短,但這裏有個任務,尚未運行呢,我怎麼知道進程的運行時間呢?根本沒辦法很是準確的再當前可運行進程中找出最短的那個進程。有一種辦法就是根據進程過去的行爲進行預測,但這能證實是個好辦法嗎?
保證調度
這種調度算法就是向用戶作出明確的可行的性能保證,而後去實現它。一種很實際的可實現的保證就是確保N個用戶中每一個用戶都得到CPU處理能力的1/N,相似的,保證N個進程中每一個進程都得到1/N的CPU時間。
彩票調度
彩票調度算法基本思想是爲進程提供各類資源(CPU時間)的彩票,一旦須要作出調度決策時,就隨機抽出一張彩票,擁有該彩票的進程得到該資源,很明顯,擁有彩票越多的進程,得到資源的可能性越大。該算法在程序喵看來能夠理解爲股票算法,將CPU的使用權分紅若干股,假設共100股分給了3個進程,給這些進程分別分配20、30、50股,那麼它們大致上會按照股權比例(20:30:50)劃分CPU的使用。
公平分享調度
假設有系統兩個用戶,用戶1啓動了1個進程,用戶2啓動了9個進程,若是使用輪轉調度算法,那麼用戶1將得到10%的CPU時間,用戶2將得到90%的CPU時間,這對用戶來講公平嗎?若是給每一個用戶分配50%的CPU時間,那麼用戶2中的進程得到的CPU時間明顯比用戶1中的進程短,這對進程來講公平嗎?這就取決於怎麼定義公平啦?
實時系統
實時系統顧名思義,最關鍵的指標固然是實時啦:
知足截止時間:須要在規定deadline前完成做業;
可預測性:可預測性是指在系統運行的任什麼時候刻,在任何狀況下,實時系統的資源調配策略都能爲爭奪資源的任務合理的分配資源,使每一個實時任務都能獲得知足。
調度算法分類:
硬實時
必須在deadline以前完成工做,若是delay,可能會發生災難性或發生嚴重的後果;
軟實時
必須在deadline以前完成工做,但若是偶爾delay了,也能夠容忍。
調度算法:
單調速率調度
採用搶佔式、靜態優先級的策略,調度週期性任務。
每一個任務最開始都被配置好了優先級,當較低優先級的進程正在運行而且有較高優先級的進程能夠運行時,較高優先級的進程將會搶佔低優先級的進程。在進入系統時,每一個週期性任務都會分配一個優先級,週期越短,優先級越高。這種策略的理由是:更頻繁的須要CPU的任務應該被分配更高的優先級。
最先截止時間調度
根據截止時間動態分配優先級,截止時間越早的進程優先級越高。
該算法中,當一個進程能夠運行時,它應該向操做系統通知截止時間,根據截止時間的遲早,系統會爲該進程調整優先級,以便知足可運行進程的截止時間要求。它與單調速率調度算法的區別就是一個是靜態優先級,一個是動態優先級。
如何配置調度策略?
調度算法有不少種,各有優缺點,操做系統本身不多能作出最優的選擇,那麼能夠把選擇權交給用戶,由用戶根據實際狀況來選擇適合的調度算法,這就叫策略與機制分離,調度機制位於內核,調度策略由用戶進程決定,將調度算法以某種形式參數化,由用戶進程來選擇參數從而決定內核使用哪一種調度算法。
操做系統怎麼完成進程調度?
進程的每次變化都會有相應的狀態,而操做系統維護了一組狀態隊列,表示系統中全部進程的當前狀態;不一樣的狀態有不一樣的隊列,有就緒隊列阻塞隊列等,每一個進程的PCB都根據它的狀態加入到相應的隊列中,當一個進程的狀態發生變化時,它的PCB會從一個狀態隊列中脫離出來加入到另外一個狀態隊列。
注意圖中同一種狀態爲何有多個隊列呢?由於進程有優先級概念,相同狀態的不一樣隊列的優先級不一樣。
什麼是線程?
線程是進程當中的一條執行流程,這幾乎就是進程的定義,一個進程內能夠有多個子執行流程,即線程。能夠從兩個方面從新理解進程:
從資源組合的角度:進程把一組相關的資源組合起來,構成一個資源平臺環境,包括地址空間(代碼段、數據段),打開的文件等各類資源
從運行的角度:代碼在這個資源平臺上的執行流程,然而線程貌似也是這樣,可是進程比線程多了資源內容列表樣式:那就有一個公式:進程 = 線程 + 共享資源
爲何使用線程?
由於要併發編程,在許多情形中同時發生着許多活動,而某些活動有時候會被阻塞,經過將這些活動分解成能夠準並行運行的多個順序流程是必須的,而若是使用多進程方式進行併發編程,進程間的通訊也很複雜,而且維護進程的系統開銷較大:建立進程時分配資源創建PCB,撤銷進程時回收資源撤銷PCB,進程切換時保存當前進程的狀態信息。因此爲了使併發編程的開銷儘可能小,因此引入多線程編程,能夠併發執行也能夠共享相同的地址空間。並行實體擁有共享同一地址空間和全部可用數據的能力,這是多進程模型所不具有的能力。
使用線程有以下優勢:
能夠多個線程存在於同一個進程中
各個線程之間能夠併發的執行
各個線程之間能夠共享地址空間和文件等資源
線程比進程更輕量級,建立線程撤銷線程比建立撤銷進程要快的多,在許多系統中,建立一個線程速度是建立一個進程速度的10-100倍。
若是多個線程是CPU密集型的,並不能很好的得到更好的性能,但若是多個線程是IO密集型的,線程存在着大量的計算和大量的IO處理,有多個線程容許這些活動彼此重疊進行,從而會加快總體程序的執行速度。
但也有缺點:一旦一個線程崩潰,會致使其所屬進程的全部線程崩潰。因爲各個線程共享相同的地址空間,那麼讀寫數據可能會致使競爭關係,所以對同一塊數據的讀寫須要採起某些同步機制來避免線程不安全問題。
何時用進程、線程?
進程是資源分配單位,線程是CPU調度單位;
進程擁有一個完整的資源平臺,而線程只獨享必不可少的資源,如寄存器和棧;
線程一樣具備就緒阻塞和執行三種基本狀態,一樣具備狀態之間的轉換關係;
線程能減小併發執行的時間和空間開銷:
線程的建立時間比進程短
線程的終止時間比進程短
同一進程內的線程切換時間比進程短
因爲同一進程的各線程間共享內存和文件資源,可直接進行不經過內核的通訊
結論:能夠在強調性能時候使用線程,若是追求更好的容錯性能夠考慮使用多進程,google瀏覽器聽說就是用的多進程編程。在多CPU系統中,多線程是有益的,在這樣的系統中,一般狀況下能夠作到真正的並行。
C/C++中如何使用多線程編程?
POSIX使用以下線程封裝函數來操做線程:
C++中有std::thread和async,能夠很方便的操做多線程,示例代碼以下:
線程是如何實現的?
線程的實現可分爲用戶線程和內核線程:
用戶線程:在用戶空間實現的線程機制,它不依賴於操做系統的內核,由一組用戶級的線程庫函數來完成線程的管理,包括進程的建立終止同步和調度等。
用戶線程有以下優勢:
因爲用戶線程的維護由相應進程來完成(經過線程庫函數),不須要操做系統內核瞭解內核瞭解用戶線程的存在,可用於不支持線程技術的多進程操做系統。
每一個進程都須要它本身私有的線程控制塊列表,用來跟蹤記錄它的各個線程的狀態信息(PC,棧指針,寄存器),TCB由線程庫函數來維護;用戶線程的切換也是由線程庫函數來完成,無需用戶態/核心態切換,因此速度特別快;容許每一個進程擁有自定義的線程調度算法;但用戶線程也有缺點:阻塞性的系統調用如何實現?若是一個線程發起系統調用而阻塞,則整個進程在等待。當一個線程開始運行後,除非它主動交出CPU的使用權,不然它所在進程當中的其它線程將沒法運行;因爲時間片分配給進程,與其它進程比,在多線程執行時,每一個線程獲得的時間片較少,執行會較慢
內核線程:是指在操做系統的內核中實現的一種線程機制,由操做系統的內核來完成線程的建立終止和管理。
特色:
在支持內核線程的操做系統中,由內核來維護進程和線程的上下文信息(PCB TCB);
線程的建立終止和切換都是經過系統調用內核函數的方式來進行,由內核來完成,所以系統開銷較大;
在一個進程當中,若是某個內核線程發起系統調用而被阻塞,並不會影響其它內核線程的運行;
時間片分配給線程,多線程的進程得到更多CPU時間;
tips
因爲在內核中建立或撤銷線程的代價比較大,某些系統採起復用的方式回收線程,當某個線程被撤銷時,就把它標記不可運行,可是內核數據結構沒有受到任何影響,若是後續又須要建立一個新線程時,就從新啓動被標記爲不可運行的舊線程,從而節省一些開銷。
注意
儘管使用內核線程能夠解決不少問題,但還有些問題,例如:當一個多線程的進程建立一個新的進程時會發生什麼?新進程是擁有與原進程相同數量的線程仍是隻有一個線程?在不少狀況下,最好的選擇取決於進程計劃下一步作什麼?若是它要調用exec啓動一個新程序,或許一個線程正合適,但若是它繼續運行,那麼最好複製全部的線程。
輕量級進程:它是內核支持的用戶線程模型,一個進程能夠有多個輕量級進程,每一個輕量級進程由一個單獨的內核線程來支持。
在Linux下是沒有真正的線程的,它所謂的線程其實就是使用進程來實現的,就是所謂的輕量級進程,其實就是進程,都是經過clone接口調用建立的,只不過二者傳遞的參數不一樣,經過參數決定子進程和父進程共享的資源種類和數量,進而有了普通進程和輕量級進程的區別。
什麼是上下文切換?
上下文切換指的是操做系統中止當前運行進程(從運行狀態改變成其它狀態)而且調度其它進程(就緒態轉變成運行狀態)。操做系統必須在切換以前存儲許多部分的進程上下文,必須可以在以後恢復他們,因此進程不能顯示它曾經被暫停過,同時切換上下文這個過程必須快速,由於上下文切換操做是很是頻繁的。那上下文指的是什麼呢?指的是任務全部共享資源的工做現場,每個共享資源都有一個工做現場,包括用於處理函數調用、局部變量分配以及工做現場保護的棧頂指針,和用於指令執行等功能的各類寄存器。
注意
這裏所說的進程切換致使上下文切換其實不太準確,準確的說應該是任務的切換致使上下文切換,這裏的任務能夠是進程也能夠是線程,準確的說線程纔是CPU調度的基本單位,可是由於各個資料都這麼解釋上下文切換,因此上面也暫時這麼介紹,只要讀者內心有這個概念就好。
進程間通訊有幾種方式?
因爲各個進程不共享相同的地址空間,任何一個進程的全局變量在另外一個進程中都不可見,因此若是想要在進程之間傳遞數據就須要經過內核,在內核中開闢出一塊區域,該區域對多個進程均可見,便可用於進程間通訊。有讀者可能有疑問了,文件方式也是進程間通訊啊,也要在內核開闢區域嗎?這裏說的內核區域實際上是一段緩衝區,文件方式傳輸數據也有內核緩衝區的參與(零拷貝除外)。
如何開闢這種公共區域來進行進程間通訊呢?
匿名管道
匿名管道就是pipe,pipe只能在父子進程間通訊,並且數據只能單向流動(半雙工通訊)。
使用方式:
1)父進程建立管道,會獲得兩個文件描述符,分別指向管道的兩端;
2)父進程建立子進程,從而子進程也有兩個文件描述符指向同一管道;
3)父進程可寫數據到管道,子進程就可從管道中讀出數據,從而實現進程間通訊,下面的示例代碼中經過pipe實現了每秒鐘父進程向子進程都發送消息的功能。
咱們平時也常用關於管道的命令行:
ls| less
該命令行的流向圖以下:
1:建立管道
2:爲ls建立一個進程,設置stdout爲管理寫端
3:爲less建立一個進程,設置stdin爲管道讀端
高級管道
經過popen將另外一個程序看成一個新的進程在當前進程中啓動,它算做當前進程的子進程,高級管道只能用在有親緣關係的進程間通訊,這種親緣關係一般指父子進程,下面的GetCmdResult函數能夠獲取某個Linux命令執行的結果,實現方式就是經過popen。
命名管道
匿名管道有個缺點就是通訊的進程必定要有親緣關係,而命名管道就不須要這種限制。
命名管道其實就是一種特殊類型的文件,所謂的命名其實就是文件名,文件對各個進程均可見,經過命名管道建立好特殊文件後,就能夠實現進程間通訊。
能夠經過mkfifo建立一個特殊的類型的文件,參數讀者看名字應該就瞭解,一個是文件名,一個是文件的讀寫權限:
當返回值爲0時,表示該命名管道建立成功,至於如何通訊,其實就是個讀寫文件的問題!
消息隊列
隊列想必你們都知道,像FIFO同樣,這裏能夠有多個進程寫入數據,也能夠有多個進程從隊列裏讀出數據,但消息隊列有一點比FIFO還更高級,它讀消息不必定要使用先進先出的順序,每一個消息能夠賦予類型,能夠按消息的類型讀取,不是指定類型的數據還存在隊列中。本質上MessageQueue是存放在內核中的消息鏈表,每一個消息隊列鏈表會由消息隊列標識符表示,這個消息隊列存於內核中,只有主動的刪除該消息隊列或者內核重啓時,消息隊列纔會被刪除。
在Linux中消息隊列相關的函數調用以下:
示例代碼以下:
代碼中爲了演示方便使用消息隊列進行的線程間通訊,該代碼一樣用於進程間通訊,消息隊列的實現依賴於內核的支持,上述代碼可能在某些系統(WSL)上不能運行,在正常的Ubuntu上能夠正常運行。
消息隊列VS命名管道
消息隊列>命名管道
1)消息隊列收發消息自動保證了同步,不須要由進程本身來提供同步方法,而命名管道須要自行處理同步問題;
2)消息隊列接收數據能夠根據消息類型有選擇的接收特定類型的數據,不須要像命名管道同樣默認接收數據。
消息隊列<命名管道
消息隊列有一個缺點就是發送和接收的每一個數據都有最大長度的限制。
共享內存
可開闢中一塊內存,用於各個進程間共享,使得各個進程能夠直接讀寫同一塊內存空間,就像線程共享同一塊地址空間同樣,該方式基本上是最快的進程間通訊方式,由於沒有系統調用干預,也沒有數據的拷貝操做,但因爲共享同一塊地址空間,數據競爭的問題就會出現,須要本身引入同步機制解決數據競爭問題。
共享內存只是一種方式,它的實現方式有不少種,主要的有mmap系統調用、Posix共享內存以及System V共享內存等。經過這三種「工具」共享地址空間後,通訊的目的天然就會達到。
信號
信號也是進程間通訊的一種方式,信號能夠在任什麼時候候發送給某一個進程,若是進程當前並未處於執行狀態,內核將信號保存,直到進程恢復到執行態再發送給進程,進程能夠對信號設置預處理方式,若是對信號設置了阻塞處理,則信號的傳遞會被延遲直到阻塞被取消,若是進程結束,那信號就被丟棄。咱們經常使用的CTRL+C和kill等就是信號的一種,也達到了進程間通訊的目的,進程也能夠對信號設置signal捕獲函數自定義處理邏輯。這種方式有很大的缺點:只有通知的做用,通知了一下消息的類型,但不能傳輸要交換的任何數據。
Linux系統中常見的信號有:
SIGHUP:該信號在用戶終端結束時發出,一般在中斷的控制進程結束時,全部進程組都將收到該信號,該信號的默認操做是終止進程;
SIGINT:程序終止信號,一般的CTRL+C產生該信號來通知終止進程;
SIGQUIT:相似於程序錯誤信號,一般的CTRL+\產生該信號通知進程退出時產生core文件;
SIGILL:執行了非法指令,一般數據段或者堆棧溢出可能產生該信號;
SIGTRAP:供調試器使用,由斷電指令或其它陷阱指令產生;
SIGABRT:使程序非正常結束,調用abort函數會產生該信號;
SIGBUS:非法地址,一般是地址對齊問題致使,好比訪問一個4字節長的整數,但其地址不是4的倍數;
SIGSEGV:合理地址的非法訪問,訪問了未分配的內存或者沒有權限的內存區域;
SIGPIPE:管道破裂信號,socket通訊時常常會遇到,進程寫入了一個無讀者的管道;
SIGALRM:時鐘定時信號,由alarm函數設置的時間終止時產生;
SIGFPE:出現浮點錯誤(好比除0操做);
SIGKILL:殺死進程(不能被捕捉和忽略);
信號量
想必你們都聽過信號量,信號量就是一個特殊的變量,程序對其訪問都是原子操做,每一個信號量開始都有個初始值。最簡單最多見的信號量是隻能取0和1的變量,也叫二值信號量。
信號量有兩個操做,P和V:
P:若是信號量變量值大於0,則變量值減1,若是值爲0,則阻塞進程;
V:若是有進程阻塞在該信號量上,則喚醒阻塞的進程,若是沒有進程阻塞,則變量值加1
互斥量用於互斥,信號量用於同步,互斥指的是某一資源同一時間只容許一個訪問者訪問,但沒法限制訪問順序,訪問是無序的,而同步在互斥的基礎上能夠控制訪問者對資源的順序。
套接字:就是網絡傳輸,不用多說,網絡通訊均可以多機通訊呢,更不用說進程間通訊啦,你能看到程序喵的文章也是套接字的功勞。
文件:顯而易見,多個進程能夠操做同一個文件,因此也能夠經過文件來進行進程間通訊。
關於進程和線程的知識點介紹就到這裏,程序喵爲了寫本文花費了半月以上時間,相信你弄懂這19個問題,面試不用愁!
另外若是你想更好的提高你的編程能力,學好C語言C++編程!彎道超車,快人一步!筆者這裏或許能夠幫到你~
C語言C++編程學習交流圈子,QQ羣1030652847【點擊進入】微信公衆號:C語言編程學習基地
分享(源碼、項目實戰視頻、項目筆記,基礎入門教程)
歡迎轉行和學習編程的夥伴,利用更多的資料學習成長比本身琢磨更快哦!
編程學習書籍分享:
編程學習視頻分享: