Pythonでの並行性-マルチプロセッシング

この章では、マルチプロセッシングとマルチスレッドの比較に焦点を当てます。

マルチプロセッシング

これは、単一のコンピューターシステム内で2つ以上のCPUユニットを使用することです。コンピュータシステムで利用可能なCPUコアの全数を利用することにより、ハードウェアから最大限の可能性を引き出すことが最善のアプローチです。

マルチスレッド

これは、複数のスレッドを同時に実行することによってオペレーティングシステムの使用を管理するCPUの機能です。マルチスレッドの主なアイデアは、プロセスを複数のスレッドに分割することによって並列処理を実現することです。

次の表は、それらの間の重要な違いのいくつかを示しています-

マルチプロセッシング マルチプログラミング
マルチプロセッシングとは、複数のCPUが同時に複数のプロセスを処理することです。 マルチプログラミングは、複数のプログラムを同時にメインメモリに保持し、単一のCPUを使用してそれらを同時に実行します。
複数のCPUを利用します。 シングルCPUを使用しています。
並列処理が可能です。 コンテキストの切り替えが行われます。
ジョブの処理にかかる時間が短縮されます。 ジョブの処理にかかる時間が長くなります。
それはコンピュータシステムの装置の非常に効率的な利用を容易にします。 マルチプロセッシングよりも効率が悪い。
通常はより高価です。 そのようなシステムはより安価です。

グローバルインタプリタロック(GIL)の影響を排除する

並行アプリケーションで作業している間、Pythonには次のような制限があります。 GIL (Global Interpreter Lock)。GILではCPUの複数のコアを利用することはできません。したがって、Pythonには真のスレッドはないと言えます。GILはミューテックス–相互排除ロックであり、スレッドセーフになります。言い換えれば、GILは複数のスレッドがPythonコードを並行して実行することを防いでいると言えます。ロックは一度に1つのスレッドのみが保持でき、スレッドを実行する場合は、最初にロックを取得する必要があります。

マルチプロセッシングを使用することで、GIL-によって引き起こされる制限を効果的に回避できます。

  • マルチプロセッシングを使用することで、複数のプロセスの機能を利用しているため、GILの複数のインスタンスを利用しています。

  • このため、プログラム内の1つのスレッドのバイトコードを一度に実行するという制限はありません。

Pythonでプロセスを開始する

次の3つの方法を使用して、マルチプロセッシングモジュール内でPythonでプロセスを開始できます。

  • Fork
  • Spawn
  • Forkserver

Forkを使用したプロセスの作成

Forkコマンドは、UNIXにある標準コマンドです。子プロセスと呼ばれる新しいプロセスを作成するために使用されます。この子プロセスは、親プロセスと呼ばれるプロセスと同時に実行されます。これらの子プロセスも親プロセスと同一であり、親が使用できるすべてのリソースを継承します。Fork −を使用してプロセスを作成する際に、次のシステムコールが使用されます。

  • fork()−一般的にカーネルに実装されているシステムコールです。プロセスのコピーを作成するために使用されます。p>

  • 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を使用したプロセスの作成

スポーンとは、何か新しいことを始めることを意味します。したがって、プロセスの生成とは、親プロセスによる新しいプロセスの作成を意味します。親プロセスは非同期で実行を継続するか、子プロセスが実行を終了するまで待機します。プロセスを生成するには、次の手順に従います-

  • マルチプロセッシングモジュールのインポート。

  • オブジェクトプロセスの作成。

  • 呼び出してプロセスアクティビティを開始します start() 方法。

  • プロセスが作業を終了するまで待機し、を呼び出して終了します join() 方法。

次のPythonスクリプトの例は、3つのプロセスを生成するのに役立ちます

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モジュールを使用すると、デーモンオプションを介してデーモンプロセスを使用できます。デーモンプロセスまたはバックグラウンドで実行されているプロセスは、デーモンスレッドと同様の概念に従います。プロセスをバックグラウンドで実行するには、デーモンフラグをtrueに設定する必要があります。デーモンプロセスは、メインプロセスが実行されている限り実行を継続し、実行が終了した後、またはメインプログラムが強制終了されたときに終了します。

ここでは、デーモンスレッドで使用されているのと同じ例を使用しています。唯一の違いは、からのモジュールの変更です。multithreadingmultiprocessingデーモンフラグを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と呼ばれるプロセスIDがあります。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マルチプロセッシングモジュール–プールクラス

単純な並列について話す場合 processingPythonアプリケーションのタスク、次にマルチプロセッシングモジュールがPoolクラスを提供します。次の方法Pool クラスは、メインプログラム内の子プロセスの数をスピンアップするために使用できます

apply()メソッド

この方法は、.submit()の方法 .ThreadPoolExecutor.結果の準備ができるまでブロックします。

apply_async()メソッド

タスクの並列実行が必要な場合は、apply_async()タスクをプールに送信するメソッド。これは、すべての子プロセスが実行されるまでメインスレッドをロックしない非同期操作です。

map()メソッド

のように apply()メソッドでは、結果の準備ができるまでブロックします。ビルトインと同等ですmap() 反復可能なデータをいくつかのチャンクに分割し、個別のタスクとしてプロセスプールに送信する関数。

map_async()メソッド

それはの変種です map() としての方法 apply_async() にあります apply()方法。結果オブジェクトを返します。結果の準備が整うと、callableが適用されます。呼び出し可能オブジェクトはすぐに完了する必要があります。そうしないと、結果を処理するスレッドがブロックされます。

次の例は、並列実行を実行するためのプロセスプールを実装するのに役立ちます。数の二乗の簡単な計算は、square() を通じて機能する multiprocessing.Pool方法。次にpool.map() 入力は0から4までの整数のリストであるため、5を送信するために使用されています。結果はに格納されます。 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]