多進程
在本章中,我們將更多地關注多處理和多線程之間的比較。
多進程
在一臺計算機系統中使用兩個或多個CPU單元。 通過利用計算機系統中可用的全部CPU核心,這是最好的方法來充分利用我們的硬件。
多線程
這是CPU通過同時執行多個線程來管理操作系統使用的能力。 多線程的主要思想是通過將進程分成多個線程來實現並行性。
下表顯示了它們之間的一些重要區別 -
編號
多進程
多程序
1
多處理是指多個CPU同時處理多個進程。
多程序同時在主存儲器中保存多個程序,並使用單個CPU同時執行它們。
2
它利用多個CPU。
它利用單個CPU
3
它允許並行處理。
上下文切換。
4
處理工作的時間更少。
處理工作需要花費更多的時間。
5
它有助於計算機系統設備的高效利用。
效率低於多重處理。
6
系統通常更昂貴。
這樣的系統更便宜。
消除全局解釋器鎖定(GIL)的影響
在使用併發應用程序時,Python中存在一個名爲GIL(全局解釋器鎖)的限制。 GIL從來不允許我們利用CPU的多個內核,因此可以說Python中沒有真正的線程。 GIL是互斥鎖 - 互斥鎖,它使線程安全。 換句話說,可以說GIL阻止了多個線程並行執行Python代碼。鎖一次只能由一個線程保存,如果想執行一個線程,那麼它必須先獲取鎖。
通過使用多處理,可以通過GIL有效地繞過 -
- 通過使用多處理,利用多個進程的能力,因此使用GIL的多個實例。
- 由於這個原因,在程序中一次執行一個線程的字節碼沒有限制。
在Python中啓動進程
可以使用以下三種方法在多處理模塊內用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意味着開始新的事物。 因此,產生一個過程意味着父過程創建一個新進程。 父進程異步繼續執行或等待子進程結束其執行。 按照這些步驟產生一個進程 -
- 導入多處理模塊。
- 創建對象進程。
- 通過調用
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 Pipes傳遞文件描述符的所選UNIX平臺。 考慮以下幾點來理解Forkserver機制的工作 -
- 服務器通過使用Forkserver機制來啓動新進程。
- 然後服務器接收命令並處理創建新進程的所有請求。
- 要創建一個新的進程,python程序會向Forkserver發送一個請求,之後它會創建一個進程。
- 最後,我們可以在程序中使用這個新創建的進程。
守護進程如何在Python中進行處理
Python多處理模塊允許通過它的守護進程選項來守護進程。 守護進程或在後臺運行的進程遵循與守護進程線程類似的概念。 要在後臺執行該進程,需要將守護進程標誌設置爲true
。 只要主進程正在執行,守護進程將繼續運行,並在完成執行或主程序被終止後終止進程。
示例
在這裏,我們使用與守護進程線程中使用的相同的示例。 唯一的區別是模塊從多線程更改爲多處理,並將守護標誌設置爲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應用程序中討論簡單的並行處理任務,那麼多處理模塊提供了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]