多進程


在本章中,我們將更多地關注多處理和多執行緒之間的比較。

多進程

在一台計算機系統中使用兩個或多個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()方法
該方法與ThreadPoolExecutorsubmit()方法類似,直到結果準備就緒。

apply_async()方法
當需要並行執行任務時,需要使用apply_async()方法將任務提交給池。 這是一個非同步操作,直到執行完所有的子進程之後才會鎖定主執行緒。

map()方法
就像apply()方法一樣,它也會阻塞直到結果準備就緒。 它相當於內建的map()函式,它將多個塊中的可疊代資料分開並作為單獨的任務提交給進程池。

map_async()方法
它是map()方法的一個變體,apply_async()apply()方法的變體。 它返回一個結果物件。 當結果準備就緒時,就會應用一個可呼叫物件。 可呼叫函式必須立即完成; 否則,處理結果的執行緒將被阻止。

例子

以下範例實現執行並行執行的進程池。 通過multiprocessing.Pool方法應用square()函式,可以簡單計算數位的平方。 然後使用pool.map()提交5,因為輸入是從04的整數列表。結果將被儲存在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]