
Python 併發程式設計 - 多程序
本章我們將重點比較多程序和多執行緒。
多程序
它是在單個計算機系統中使用兩個或多個 CPU 單元。這是充分發揮硬體潛力的最佳方法,可以利用計算機系統中可用的所有 CPU 核心。
多執行緒
這是 CPU 管理作業系統使用情況的能力,透過併發執行多個執行緒來實現。多執行緒的主要思想是透過將程序劃分為多個執行緒來實現並行。
下表顯示了它們之間的一些重要區別:
多程序 | 多程式設計 |
---|---|
多程序指的是多個 CPU 同時處理多個程序。 | 多程式設計同時在主記憶體中保留多個程式,並利用單個 CPU 併發執行它們。 |
它利用多個 CPU。 | 它利用單個 CPU。 |
它允許並行處理。 | 發生上下文切換。 |
處理作業所需時間更少。 | 處理作業所需時間更多。 |
它有助於更有效地利用計算機系統的裝置。 | 效率低於多程序。 |
通常更昂貴。 | 此類系統成本較低。 |
消除全域性直譯器鎖 (GIL) 的影響
在處理併發應用程式時,Python 中存在一個名為GIL(全域性直譯器鎖)的限制。GIL 從不允許我們利用 CPU 的多個核心,因此可以說 Python 中沒有真正的執行緒。GIL 是互斥鎖,它使事物執行緒安全。換句話說,我們可以說 GIL 阻止多個執行緒並行執行 Python 程式碼。一次只能由一個執行緒持有鎖,如果要執行執行緒,則必須首先獲取鎖。
使用多程序,我們可以有效地繞過 GIL 造成的限制:
透過使用多程序,我們正在利用多個程序的能力,因此我們正在利用 GIL 的多個例項。
因此,在任何時候都沒有限制在我們的程式中執行一個執行緒的位元組碼。
在 Python 中啟動程序
可以使用以下三種方法在 multiprocessing 模組中啟動 Python 中的程序:
- fork
- spawn
- forkserver
使用 fork 建立程序
fork 命令是 UNIX 中的標準命令。它用於建立稱為子程序的新程序。此子程序與稱為父程序的程序併發執行。這些子程序與其父程序也相同,並繼承父程序可用的所有資源。建立具有 fork 的程序時,使用以下系統呼叫:
fork() - 它通常在核心中實現的系統呼叫。它用於建立程序的副本。
getpid() - 此係統呼叫返回呼叫程序的程序 ID (PID)。
示例
下面的 Python 指令碼示例將幫助您瞭解如何建立新的子程序並獲取子程序和父程序的 PID:
import os def child(): n = os.fork() if n > 0: print("PID of Parent process is : ", os.getpid()) else: print("PID of Child process is : ", os.getpid()) child()
輸出
PID of Parent process is : 25989 PID of Child process is : 25990
使用 spawn 建立程序
spawn 意味著啟動新事物。因此,生成程序意味著父程序建立新程序。父程序繼續非同步執行或等待子程序結束執行。按照以下步驟生成程序:
匯入 multiprocessing 模組。
建立程序物件。
透過呼叫start()方法啟動程序活動。
等待程序完成其工作並透過呼叫join()方法退出。
示例
下面的 Python 指令碼示例有助於生成三個程序
import multiprocessing def spawn_process(i): print ('This is process: %s' %i) return if __name__ == '__main__': Process_jobs = [] for i in range(3): p = multiprocessing.Process(target = spawn_process, args = (i,)) Process_jobs.append(p) p.start() p.join()
輸出
This is process: 0 This is process: 1 This is process: 2
使用 forkserver 建立程序
forkserver 機制僅適用於支援透過 Unix 管道傳遞檔案描述符的某些 UNIX 平臺。請考慮以下幾點,以瞭解 forkserver 機制的運作方式:
使用 forkserver 機制啟動新程序時,會例項化一個伺服器。
然後,伺服器接收命令並處理所有建立新程序的請求。
為了建立一個新程序,我們的 Python 程式將向 forkserver 傳送請求,它將為我們建立一個程序。
最後,我們可以在程式中使用這個新建立的程序。
Python 中的守護程序
Python 的multiprocessing模組允許我們透過其 daemonic 選項擁有守護程序。在後臺執行的守護程序或程序遵循與守護執行緒類似的概念。要執行後臺程序,我們需要將 daemonic 標誌設定為 true。守護程序將在主程序執行期間繼續執行,並在完成執行或主程式被終止後終止。
示例
這裡,我們使用與守護執行緒中使用的相同的示例。唯一的區別是將模組從multithreading更改為multiprocessing並將 daemonic 標誌設定為 true。但是,輸出將發生變化,如下所示:
import multiprocessing import time def nondaemonProcess(): print("starting my Process") time.sleep(8) print("ending my Process") def daemonProcess(): while True: print("Hello") time.sleep(2) if __name__ == '__main__': nondaemonProcess = multiprocessing.Process(target = nondaemonProcess) daemonProcess = multiprocessing.Process(target = daemonProcess) daemonProcess.daemon = True nondaemonProcess.daemon = False daemonProcess.start() nondaemonProcess.start()
輸出
starting my Process ending my Process
與守護執行緒生成的輸出相比,輸出不同,因為非守護模式下的程序沒有輸出。因此,守護程序在主程式結束後會自動結束,以避免持久執行程序。
在 Python 中終止程序
我們可以使用terminate()方法立即終止程序。我們將使用此方法在完成執行之前立即終止使用函式建立的子程序。
示例
import multiprocessing import time def Child_process(): print ('Starting function') time.sleep(5) print ('Finished function') P = multiprocessing.Process(target = Child_process) P.start() print("My Process has terminated, terminating main thread") print("Terminating Child Process") P.terminate() print("Child Process successfully terminated")
輸出
My Process has terminated, terminating main thread Terminating Child Process Child Process successfully terminated
輸出顯示程式在使用 Child_process() 函式建立的子程序執行之前終止。這意味著子程序已成功終止。
識別 Python 中的當前程序
作業系統中的每個程序都有稱為 PID 的程序標識。在 Python 中,我們可以使用以下命令找到當前程序的 PID:
import multiprocessing print(multiprocessing.current_process().pid)
示例
下面的 Python 指令碼示例有助於找出主程序的 PID 以及子程序的 PID:
import multiprocessing import time def Child_process(): print("PID of Child Process is: {}".format(multiprocessing.current_process().pid)) print("PID of Main process is: {}".format(multiprocessing.current_process().pid)) P = multiprocessing.Process(target=Child_process) P.start() P.join()
輸出
PID of Main process is: 9401 PID of Child Process is: 9402
在子類中使用程序
我們可以透過子類化threading.Thread類來建立執行緒。此外,我們還可以透過子類化multiprocessing.Process類來建立程序。要在子類中使用程序,我們需要考慮以下幾點:
我們需要定義Process類的新的子類。
我們需要重寫_init_(self [,args] )類。
我們需要重寫run(self [,args] )方法來實現Process的功能。
我們需要透過呼叫start()方法啟動程序。
示例
import multiprocessing class MyProcess(multiprocessing.Process): def run(self): print ('called run method in process: %s' %self.name) return if __name__ == '__main__': jobs = [] for i in range(5): P = MyProcess() jobs.append(P) P.start() P.join()
輸出
called run method in process: MyProcess-1 called run method in process: MyProcess-2 called run method in process: MyProcess-3 called run method in process: MyProcess-4 called run method in process: MyProcess-5
Python 多程序模組 – Pool 類
如果我們在 Python 應用程式中討論簡單的並行處理任務,那麼 multiprocessing 模組為我們提供 Pool 類。Pool類的以下方法可用於在主程式中啟動多個子程序
apply() 方法
此方法類似於.ThreadPoolExecutor的.submit()方法。它會阻塞,直到結果準備就緒。
apply_async() 方法
當我們需要並行執行任務時,我們需要使用apply_async()方法將任務提交到池。這是一個非同步操作,在所有子程序執行完畢之前不會鎖定主執行緒。
map() 方法
與apply()方法一樣,它也會阻塞,直到結果準備就緒。它等同於內建的map()函式,該函式將可迭代資料分成多個塊,並將其作為單獨的任務提交到程序池。
map_async() 方法
它是map()方法的一個變體,就像apply_async()對於apply()方法一樣。它返回一個結果物件。當結果準備就緒時,會對其應用一個可呼叫物件。可呼叫物件必須立即完成;否則,處理結果的執行緒將被阻塞。
示例
下面的示例將幫助您實現一個用於執行並行執行的程序池。透過multiprocessing.Pool方法應用square()函式來執行簡單的數字平方計算。然後使用pool.map()提交 5,因為輸入是從 0 到 4 的整數列表。結果將儲存在p_outputs中並打印出來。
def square(n): result = n*n return result if __name__ == '__main__': inputs = list(range(5)) p = multiprocessing.Pool(processes = 4) p_outputs = pool.map(function_square, inputs) p.close() p.join() print ('Pool :', p_outputs)
輸出
Pool : [0, 1, 4, 9, 16]