Python課程筆記(13): Concurrency Programming - Process

Ian
Mar 10, 2021

--

介紹一下Process的概念,以及在Python中如何使用Process

Process

什麼是Process

我們所寫的程式或是一般的應用程式稱為Program,當我們啟動Program時,作業系統會將這些Program讀取到記憶體中再交由CPU執行,這時就變成了Process,所以Process其實就是正在進行中的Program。

Process的特性

  1. 是作業系統分配資源的最小單位,每一個Process都擁有一個獨立的PID,裡面都有包含一些資源,例如CPU、記憶體等等。
  2. Process和Process間是互相獨立的

名詞解釋

Concurrency (並發) vs Parallelism (並行)

我們在操作電腦時,通常會同時開啟很多應用程式(Browser, Spotify…),也就是會同時存在著很多個Process要給CPU執行,但是CPU一次只能執行一個Process,它是如何做到同時處理這些Process的呢 ?

這邊要講到兩個名詞,Concurrency和Parallelism

Concurrency: 兩個Process依序給同一個CPU處理,但因為CPU切換處理兩個Process的速度太快了,讓我們看起來是同時執行的。

CPU會切換不同Process的時機有兩個

  1. 當Process遇到IO操作時,需要等待IO操作完成,這時作業系統就會做切換,去執行另一個Process。
  2. 當一個Process長期佔用CPU時,作業系統也會將其切換到另一個Process。

Parallelism: 真正的同時執行多個Process,因為一個CPU同時只能執行一個Process,所以必須要有多個CPU才辦得到。

Synchronous (同步) vs Asynchronous (非同步)

這兩個名詞描述的是任務的提交模式。

Synchronous: 任務提交完後,必須原地等待任務執行的結果,等待期間不做任何事情。

Asynchronous: 任務提交後,不原地等待任務執行的結果,直接去做其他事情,通常會用一個異步回調 (Callback)機制來處理返回的結果。

blocking (阻塞) vs non-blocking (非阻塞)

這兩個名詞描述的是Process的運行狀態。

blocking: 狀態圖中的Blocked State

non-blocking: 狀態圖中的Ready、Running State

Process 運行的狀態圖

https://www.researchgate.net/figure/Three-state-transition-diagram_fig6_268347340
  • Running: Process正在被CPU執行
  • Ready: Process正在等待被CPU執行
  • Blocked: Process被IO阻塞住
  • Running -> Ready: 當CPU切換到另一個Process時,該Process進入Ready狀態。
  • Ready -> Running: 當CPU重新執行該Process,又回到Running狀態。
  • Running -> Blocked: 當Process遇到IO被阻塞時,進入Blocked狀態。
  • Blocked -> Ready: 當IO事件完成時,進入Ready狀態,等待CPU重新執行它。

上述名詞可以互相組合,最高效的組合是異步非阻塞模型。

在Python中使用Process

產生Process

在Python中可以使用multiprocessing的Library來產生多個Process。創建Process就是在記憶體中申請一塊記憶體空間將所需執行的程式碼丟進去,
一個行程對應在記憶體中就是一塊獨立的記憶體空間,多個行程對應在記憶體中就是多塊獨立的記憶體空間。Process與Process之間數據預設是無法進行交互的。

Join

Join方法讓主Process中的程式碼等待子Process結束後再繼續進行,但不影響其他子Process的運行。

Zombie Process(殭屍行程)、orphan Process(孤兒行程)和daemon Process (守護行程)

Zoombie Process: 沒有做任何事,只是佔著並耗用系統資源就是殭屍行程,產生這種行程的原因為,父行程開了子行程,該子行程完成後,因為要讓父行程能夠察看到它開設的子行程的一些訊息,例如占用的PID號或是運行時間等等,所以子行程不會立即釋放所占用的PID和資源。

所有行程都會有短暫的殭屍行程階段,最後才由父行程回收這些PID號。所以如果不斷地開設子行程,又還沒回收的話,系統的PID會被占用光,就沒有PID可以使用了。

Orphan Process: 子行程還存活著,但父行程卻意外死亡的話,這些子行程就稱為孤兒行程,作業系統會開設一個地方專門管理孤兒行程來回收相關資源。

Daemon Process: 當父行程死掉時,跟著一起死掉的其他行程。

行程之間的同步(加鎖)

行程之間的資料是不共享的,但是訪問同一個文件是沒有問題的,如果兩個行程同時操作某一個文件的話,可能會因為不同的訪問先後順序導致拿到的資料錯亂。要控制這種情況的話,就是要保證同時間只會有一個行程在操作這份文件,用的方式就是加鎖。

加鎖的目的就是把並行變成了串行,犧牲了執行速度但保證了資料的安全

搶票範例 (未加鎖)

搶票範例 (加鎖)

IPC (Inter-Process Communication)機制

因為行程和行程之間的資料是不共享的,如果要讓行程跟行程之間互相共享資料,彼此間溝通的話,可以用上面所講的文件來共享資料。但缺點是,必須自己去處理兩個行程同時處理文件的狀況,也就是要加鎖處理。

因此我們要找一個IPC的解決方案,它最好符合

  1. 效率高(最好是可以操作記憶體裡的資料,不用像文件到硬碟去找)
  2. 已經幫我們處理好了鎖的問題

Queue就是一個符合這兩個要求的IPC方案

生產者與消費者模型

在兩個行程溝通時,通常會有分成產生資料的行程(生產者)以及消費資料的行程(消費者)。

如果生產者生產很快,消費者消費很慢的話,那生產者就必須等到消費者消費完才能再繼續生產資料。

如果消費者消費很快,那消費者也必須等待生產者生產。

生產者消費者模式,就是用來解決這兩個腳色的強耦合問題,在此模型中,生產者與消費者不直接通信,而是透過一個queue來進行通信,也就是說生產者會把數據放到queue中,再由消費者去拿出來消耗掉。所以就好像多了一個緩衝,來平衡生產者消費者的處理能力。

生產者 < — -> Queue < — -> 消費者

行程池 (Process Pool)

要加快系統處理的速度,會希望CPU可以並行處理,而開很多個行程是其中一個方法,但是缺點是一直開設行程,反而會造成效率下降,因為開設行程也會耗費系統的資源,所以我們需要有一個控制中心來幫我們在保證計算機安全的情況下,最大化的利用行程,這就是行程池的功能

同步調用

非同步調用

--

--