[Python學習日記-84] 進程理論
簡介
進程的概念
并發與并行的區別
進程并發的實現
簡介
????????進程理論是計算機科學中一種重要的概念,用來描述操作系統中執行的程序實例。在操作系統中,每個程序的執行被稱為一個進程。進程理論研究進程的創建、調度、通信、同步和終止等方面的原理和方法。本篇我們將介紹進程的概念、進程的創建和終止,以及并發與并行的區別,并了解并發的實現。
進程的概念
一、什么是進程
????????進程是計算機中正在運行的程序的實例,大白話說就是正在進行的一個過程或者說一個任務,而負責執行任務則是 CPU。
????????每個進程都有自己獨立的內存空間,包括代碼、數據和堆棧等,使其能夠獨立運行并與其他進程隔離。操作系統通過管理進程來分配資源,確保它們能夠有效地運行并與其他進程協同工作。在多任務系統中,多個進程可以同時運行,共享計算機的處理器和內存資源。
二、進程與程序的區別
????????進程和程序是計算機領域中的兩個重要概念,它們之間有一定的區別:
程序:程序是一組指令的集合,描述了在計算機上執行特定任務的步驟和邏輯。程序是靜態的,它們通常存儲在磁盤或內存中,并且不具有執行的狀態。程序可以是源代碼、可執行文件或腳本等形式。
進程:進程是程序在計算機中執行時的實例。當程序被加載到內存中,并被操作系統調度執行時,就會形成一個進程。每個進程都擁有獨立的內存空間、堆棧和寄存器等資源,用于執行程序中的指令和處理數據。
? ? ? ? 總的來說,程序僅僅只是一堆代碼而已,而進程指的是程序的運行過程。
? ? ? ? 舉個例子,在一家有名的西餐廳中,廚師們正在為迎接顧客而做準備,他們有菜單中每道菜相應的食譜,廚房里有所需的原料(土豆、胡蘿卜、牛肉、雞肉等)。
? ? ? ? 在這個比喻中:
- 菜單中每道菜相應的食譜就是程序(即用適當形式描述的算法)
- 廚師們就是處理器(即CPU)
- 做菜用的各種原材料就是輸入的數據
- 進程就是廚師閱讀食譜,取來各種原材料以及烹飪料理等一系列動作的總和
? ? ? ? 餐廳開始營業了,顧客陸續進場,各項作業都井然有序的執行著,突然間一位顧客被鮮嫩多汁的牛排卡住了喉嚨,廚師想了想,處理被卡住喉嚨的顧客比做其他菜更加重要,于是廚師就記錄下他照著食譜做到哪兒了(保存進程的當前狀態),然后拿出一本急救手冊,按照其中的指示進行海姆立克急救法對顧客進行急救。
? ? ? ? 這里,我們看到處理機從一個進程(做菜)切換到另一個高優先級的進程(實施醫療救治),每個進程擁有各自的程序(食譜和急救手冊)。當被食物卡住喉嚨的顧客處理完之后,這位廚師又會回來繼續從他離開時的那一步開始繼續把菜做下去。
需要強調的是:同一個程序執行兩次,那也是兩個進程,例如,用 PoyPlayer(視頻播放軟件)打開兩個視頻,雖然都是同一個軟件,但是一個播放的是哆啦A夢,另一個播放的是蠟筆小新。
三、同步\異步和阻塞\非阻塞
1、同步
????????所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不會返回。按照這個定義,其實絕大多數函數都是同步調用。但是一般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。
? ? ? ? 舉個例子:
- multiprocessing.Pool 下的 apply(發起同步調用后,就在原地等著任務結束,根本不考慮任務是在計算還是在 I/O?阻塞,總之就是無腦地等任務結束,這十分容易造成死鎖現象,實際編程中應該避免這種等待)
- concurrent.futures.ProcessPoolExecutor().submit(func,).result()
- concurrent.futures.ThreadPoolExecutor().submit(func,).result()
2、異步
????????異步的概念和同步相對。當一個異步功能調用發出后,調用者不能立刻得到結果。當該異步功能完成后,通過狀態、通知或回調來通知調用者。如果異步功能用狀態來通知,那么調用者就需要每隔一定時間檢查一次,效率就很低(有些初學多線程編程的人,總喜歡用一個循環去檢查某個變量的值,這其實是一 種很嚴重的錯誤)。如果是使用通知的方式,效率則很高,因為異步功能幾乎不需要做額外的操作。至于回調函數,其實和通知沒太多區別。
? ? ? ? 舉個例子:
- multiprocessing.Pool().apply_async()(發起異步調用后,并不會等待任務結束才返回,相反,會立即獲取一個臨時結果,該結果并不是最終的結果,可能是封裝好的一個對象)
- concurrent.futures.ProcessPoolExecutor(3).submit(func,)
- concurrent.futures.ThreadPoolExecutor(3).submit(func,)
3、阻塞
????????阻塞調用是指調用結果返回之前,當前線程會被掛起(例如,遇到 I/O 操作)。函數只有在得到結果之后才會將阻塞的線程激活。有人也許會把阻塞調用和同步調用等同起來,實際上他是不同的。對于同步調用來說,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回而已。
????????舉個例子:
- 同步調用:apply 一個累計1億次的任務,該調用會一直等待,直到任務返回結果為止,但并未阻塞住(即便是被搶走 CPU 的執行權限,那也是處于就緒態)
- 阻塞調用:當 socket 工作在阻塞模式的時候,如果沒有數據的情況下調用 recv 函數,則當前線程就會被掛起,直到有數據為止
4、非阻塞
????????非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前也會立刻返回,同時該函數不會阻塞當前線程。
? ? ? ? 總的來說,同步與異步針對的是函數(任務)的調用方式。同步就是當一個進程發起一個函數(任務)調用的時候,一直等到函數(任務)完成,而進程繼續處于激活狀態;異步情況下是當一個進程發起一個函數(任務)調用的時候,不會等函數返回,而是繼續往下執行當,函數返回的時候通過狀態、通知、事件等方式通知進程任務完成。阻塞與非阻塞針對的是進程或線程。阻塞是當請求不能滿足的時候就將進程掛起;非阻塞則不會阻塞當前進程。
四、進程的創建與終止
1、創建
????????但凡是硬件,都需要有操作系統去管理,只要有操作系統,就有進程的概念,就需要有創建進程的方式,一些操作系統只為一個應用程序設計,例如,電飯煲中的控制器,一旦電飯煲啟動,電飯煲中的所有進程都會啟動,并存在于存儲當中。而對于通用系統(Windows、Linux、MacOS,之類會跑很多應用程序的),需要有系統運行過程中創建或撤銷進程的能力,主要分為四種形式創建新的進程:
- 系統初始化(查看進程:例如,Linux 中用?ps 命令查看,Windows 中用任務管理器查看。前臺進程負責與用戶交互,后臺運行的進程則與用戶無關,而運行在后臺并且只有在需要時才喚醒的進程,被稱為守護進程,例如,電子郵件、Web 頁面、新聞、打印等)
- 一個進程在運行過程中開啟了子進程(例如,nginx 開啟多進程、os.fork、subprocess.Popen 等)
- 用戶的交互式請求,而創建一個新進程(例如,用戶雙擊 QQ)
- 一個批處理作業的初始化(只在大型機的批處理系統中應用)
? ? ? ? 無論上述的哪一種,新進程的創建都是由一個已經存在的進程執行了一個用于創建進程的系統調用而創建的,而在不同系統中調用的方法也各不相同:
- 在 Unix 中該系統調用的是 fork,fork 會先創建一個與父進程一模一樣的副本,二者有相同的存儲映像、同樣的環境字符串和同樣的打開文件(在 shell 解釋器進程中,執行一個命令就會創建一個子進程)
- 在 Windows 中該系統調用的是 CreateProcess,CreateProcess 既處理進程的創建,也負責把正確的程序裝入新進程中
? ? ? ? 對于 Unix 和 Windows 創建的子進程有什么區別:
- 相同的是進程創建后,父進程和子進程有各自不同的地址空間(即多道技術要求物理層面實現進程之間內存的隔離),任何一個進程的在其地址空間中的修改都不會影響到另外一個進程
- 不同的是在 Unix 中,子進程的初始地址空間是父進程的一個副本,也就是說子進程和父進程是可以有只讀的共享內存區的。但是對于 Windows 來說,從一開始父進程與子進程的地址空間就是不同的
2、終止
? ? ? ? 對于進程的終止主要分為四種形式:
- 正常退出(自愿,如用戶點擊交互式頁面的叉號,或程序執行完畢調用發起系統調用正常退出,在 Linux 中用 exit,在 Windows 中用 ExitProcess)
- 出錯退出(自愿,執行命令 python a.py 時,命令中的 a.py 不存在)
- 嚴重錯誤(非自愿,執行非法指令,例如,引用不存在的內存地址、1/0 等,可以通過捕捉異常 try...except... 來規避)
- 被其他進程殺死(非自愿,例如,在 Linux 中使用 kill -9 殺死某個 pid)
五、進程的層次結構
????????在操作系統中,進程的層次結構是指多個進程之間的父子關系組成的層次結構。通常情況下,一個操作系統會以樹狀結構來組織多個進程之間的關系。
????????無論 Unix 還是 Windows,每個進程都有一個父進程(除了根進程,它是整個系統中的第一個進程),不同的是在 Unix 中所有的進程,都是以 init 進程為根,組成樹形結構。父子進程共同組成一個進程組,這樣當用戶從鍵盤發出一個信號時,該信號被送給當前與鍵盤相關的進程組中的所有成員。
?????????在 Windows 中,沒有進程層次的概念,所有的進程都是地位相同的,唯一類似于進程層次的暗示,是在創建進程時,父進程得到一個特別的令牌,稱為句柄,該句柄可以用來控制子進程,但是父進程有權把該句柄傳給其他子進程,這樣就沒有層次了。
六、進程的狀態
? ? ? ? 我們以一個 Unix 當中的命令為例
tail -f access.log |grep '404'
? ? ? ? 在執行程序 tail 時,開啟了一個子進程來執行程序 grep,兩個進程之間基于管道“|”通訊開啟了另外一個子進程,將 tail 的結果作為 grep 的輸入。
? ? ? ? 在這過程當中,進程 grep 在等待輸入(即 I/O)時的狀態被稱為阻塞,此時 grep 命令是沒有在運行的,在兩種情況下會導致一個進程在邏輯上不能運行:
- 進程掛起是自身原因,遇到 I/O 阻塞,便要讓出 CPU 讓其他進程去執行,這樣保證 CPU 一直在工作
- 與進程無關,是操作系統層面作出的操作,可能會因為一個進程占用時間過長,或者優先級等原因,進而調用其他的進程去使用 CPU
- 運行:當一個進程在CPU上運行時(單處理機處于運行態的進程只有一個,多進程在 CPU 上交替運行)
- 就緒:一個進程獲得了除 CPU 外的一切所需資源,一旦得到處理機即可運行
- 阻塞:阻塞也稱等待或睡眠狀態,一個進程正在等待某一事件發生(例如,請求 I/O、等待 I/O 完成等)而暫時停止運行,此時即使把 CPU 分配給進程也無法運行,故稱進程處于阻塞狀態
并發與并行的區別
????????無論是并發還是并行,在用戶看來都是“同時”運行的,不管是進程還是線程,都只是一個任務而已,真是干活的是 CPU,CPU 來做這些任務只能一個一個來,即同一時刻只能執行一個任務。
一、并發
????????并發(Concurrency)是指在一個時間段內同時處理多個任務是偽并行,即看起來是同時運行。這些任務可以交替進行,每個任務都可能在不同的時間點被執行,但在任何時刻只有一個任務在被執行。在并發中,任務之間可能會互相影響或者依賴,需要通過調度算法來決定任務的執行順序。單個 CPU 的情況下,可以利用多道技術來實現并發。
? ? ? ? 舉個例子:
- 情人節約會:有一個海王他有三個女朋友,在情人節這天碰巧都要去同一個商場約會,那沒辦法你只能硬著頭皮去了,我們把情人節這天和各個女朋友約會都看作是一個約會任務,這個海王被這三個任務共享,如果想要不出現修羅場的情況,那就要玩出并發戀愛的效果,具體操作應該是這樣的,先跟“女友1”去看電影,看了一會說:“不好,我要拉肚子”,然后跑去跟“女友2”吃飯,吃了一會說:“那啥,我去趟洗手間”,然后跑去跟“女友3”開了個房,做了一回又跑回“女友1”哪里繼續看電影,如此循環往復過完了情人節這天。
二、并行
????????并行(Parallelism)是指在同一時刻同時執行多個任務。在并行中,多個任務同時在不同的處理器上或者不同的計算機上并行執行,每個任務都有自己的執行流程。并行可以顯著提高計算效率,尤其是在處理大規模數據或者復雜計算任務時。
? ? ? ? 舉個例子:
- 假設銀行有四個柜臺,現在來了六個客戶都要辦理業務,這樣同一時間有四個任務被執行,假設分配給了柜臺1、柜臺2、柜臺3、柜臺4,這個時候所有柜臺都占滿了,客戶5和客戶6只能等著,一旦客戶1遇到需要等待家人拿資料過來,就會中斷當前服務,此時客戶5就拿到柜臺1的時間片去辦理業務了,而客戶1資料到了之后銀行會重新調用他,可能會被分配到四個柜臺的其中一個去繼續處理之前的業務
- 我們換個視角,從計算機的視角出發,四個柜臺對應四個核,分別是 CPU1、CPU2、CPU3、CPU4,六個客戶對應六個任務,這樣同一時間有四個任務被執行,假設分別被分配給了 CPU1、CPU2、CPU3、CPU4,一旦任務1遇到 I/O 就被迫中斷執行,此時任務5就拿到 CPU1 的時間片去執行(這就是單核下的多道技術,也就是說并行和多道技術并不沖突),而一旦任務1的 I/O 結束了,操作系統會重新調用它(要知道進程的調度、分配給哪個 CPU 運行,都是操作系統說了算),可能被分配給四個 CPU 中的任意一個去執行
?????????所有現代計算機經常會在同一時間做很多件事,即使是一臺個人電腦(無論是單核還是多核),都可以同時運行多個任務(一個任務可以理解為一個進程),例如后臺運行著360殺毒的同時打開著 QQ 在聊天,還打開了 PotPlay 播放電影。這些被同時運行的進程都需要被管理,這也說明了一個支持多進程的多道程序系統是多么至關重要。
? ? ? ? 多道技術的回顧請查看這篇博客:操作系統的介紹
進程并發的實現
????????進程并發的實現在于,硬件中斷一個正在運行的進程,把此時進程運行的所有狀態保存下來,為此操作系統需要維護一張表格來保存下終端的狀態,這張表被稱為進程表(Process Table),每個進程占用一個進程表項,這些表項被稱為進程控制塊
????????該表存放了進程狀態的重要信息:程序計數器、堆棧指針、內存分配狀況、所有打開文件的狀態、帳號和調度信息,以及進程由運行態轉為就緒態或者阻塞態時必須保存的信息,從而保證該進程在再次啟動時,就像從未被中斷過一樣。