Python での並列処理: multiprocessing.Process と concurrent.futures の比較
Python の multiprocessing.Process.run()
メソッドについてですが、実は 直接呼び出すことは intended (想定) されません。ちょっと紛らわしいですね。
multiprocessing.Process
クラス は、別の Python プロセスを起動するための仕組みを提供します。このプロセスは、親プロセス (メインプログラム) とは独立に、別々のメモリ空間 で実行されます。
run()
メソッド は、この新しく作成されたプロセス内で実行される関数です。しかし、直接 process.run()
を呼び出すと、親プロセスと同じスレッドで実行されてしまいます。
プロセスを起動するには、以下の手順で行います。
- ターゲット関数 を定義します。これは、新しいプロセスで実行したい実際の処理を記述した関数です。
Process
クラスをインスタンス化します。このとき、target
引数に先ほど定義したターゲット関数を渡します。start()
メソッドを呼び出します。これにより、新しいプロセスが生成され、バックグラウンドでrun()
メソッドの実行が開始されます。- 必要に応じて、
join()
メソッドを呼び出して、新しいプロセスが終了するまで待機できます。
Process
クラスをインスタンス化し、start()
メソッドでプロセスを起動しましょう。- 新しいプロセスで実行したい関数を定義しましょう。
multiprocessing.Process.run()
は 直接呼ばない でください。
- 公式ドキュメント: multiprocessing — Process-based parallelism — Python 3.13.0 documentation [invalid URL removed]
multiprocessing.Process.run()
メソッドは、実はあまり直接呼ばない方が良いとされています。ですが、概念を理解するために使った教材などで見かけるかもしれません。ここでは、このメソッドにまつわるよくあるエラーとその対処法について説明します。
直接 run() を呼び出すことによるエラー
multiprocessing.Process.run()
は、本来 別のプロセスで実行したい関数を登録するためのメソッド です。しかし、これを 直接呼び出してしまうと、親プロセスと同じスレッドで実行されてしまいます。本来のマルチプロセッシング処理にならないため、意図した結果が得られません。
対処法
- プロセスを起動するには、生成した
Process
オブジェクトのstart()
メソッドを呼び出します。 - ターゲット関数を定義し、
Process
クラスのコンストラクタに渡してインスタンス化しましょう。
ピックリングできない引数を使用することによるエラー
プロセス間でデータをやり取りするには、シリアライズ (Pickling)と呼ばれる変換が必要です。しかし、シリアライズできないオブジェクト を関数引数として渡すとエラーが発生します。
対処法
- シリアライズできないライブラリを使用している場合は、別途工夫が必要になる場合があります。
- シリアライズできないオブジェクトの使用を避けるか、シリアライズ可能な形式に変換してから渡しましょう。
子プロセス側でのモジュールインポートエラー
親プロセスで読み込んだモジュールを、子プロセスでもそのまま利用できるわけではありません。子プロセスは独立したメモリ空間を持つため、モジュールを新たにインポートする必要があります。
対処法
- 子プロセス内で使用するモジュールを明示的にインポートしましょう。
子プロセス側の標準出力の扱いに注意
子プロセスの標準出力は、そのまま親プロセス側に出力されません。子プロセスの出力を取得したい場合は、Queue
や Pipe
などを使って親プロセスに送る必要があります。
注意
multiprocessing.Process.run()
メソッドは 直接呼び出すことは intended (想定) されません。新しいプロセスを起動するための仕組みを提供する multiprocessing.Process
クラスの内部メソッドです。
実用的なコード例 としては、以下の手順でプロセスを起動する方法が一般的です。
- ターゲット関数 を定義します。
Process
クラスをインスタンス化し、target
引数にターゲット関数を渡します。start()
メソッドを呼び出して、プロセスを起動します。
以下はコード例です。
from multiprocessing import Process
def square(num):
"""引数の平方を計算する関数"""
return num * num
if __name__ == "__main__":
# ターゲット関数定義
process = Process(target=square, args=(5,)) # 引数として 5 を渡す
# プロセス起動
process.start()
# プロセス終了を待機 (非推奨)
# process.join() #
# 親プロセス側で何か処理をする
print("親プロセス: 終了を待機せずに処理を続ける")
解説
square
関数は、引数の平方を計算します。Process
クラスをインスタンス化し、target
引数にsquare
関数を、args
引数にタプル(5,)
を渡します。タプル(5,)
は、関数に渡す引数を表し、ここでは5
を渡します。start()
メソッドを呼び出して、プロセスを起動します。
- 子プロセスの標準出力を親プロセスで取得したい場合は、
Queue
やPipe
などを使って通信を行う必要があります。 process.join()
メソッドは、子プロセスが終了するまで親プロセスを待機させます。実務では、親プロセスは子プロセスの終了を待機せずに、他の処理を進めることが多いです (非推奨とされていますが、コード例として残しました)。
概念理解のために run()
メソッドを使った (誤った) コード例
このコードは 実用ではなく、概念理解のためのものです。
from multiprocessing import Process
def square(num):
"""引数の平方を計算する関数"""
return num * num
if __name__ == "__main__":
# ターゲット関数定義
process = Process(target=square, args=(5,))
# プロセス起動 (誤り)
process.run() # 直接 run() を呼び出す (本来は誤り)
# 親プロセス側で何か処理をする
print("親プロセス: 終了を待機せずに処理を続ける")
このコードでは、process.run()
を直接呼び出していますが、これは 本来の目的通りには動作しません。親プロセスと同じスレッドで実行されてしまうため、マルチプロセッシングの効果が得られません。
multiprocessing.Process.run()
メソッドは、直接呼び出さず、別の方法でプロセスを起動しましょう。ここでは、マルチプロセッシングにおける代替方法を紹介します。
multiprocessing.Process クラスの使用
最も一般的な方法は、 multiprocessing.Process
クラスを使ってプロセスを起動する方法です。以下の手順で行います。
- ターゲット関数 を定義します。これは、新しいプロセスで実行したい処理を記述した関数です。
Process
クラスをインスタンス化します。このとき、target
引数に先ほど定義したターゲット関数を渡します。start()
メソッドを呼び出して、新しいプロセスを生成し、バックグラウンドで実行を開始します。
コード例
from multiprocessing import Process
def square(num):
"""引数の平方を計算する関数"""
return num * num
if __name__ == "__main__":
# ターゲット関数定義
process = Process(target=square, args=(5,))
# プロセス起動
process.start()
# 親プロセス側で何か処理をする
print("親プロセス: 終了を待機せずに処理を続ける")
concurrent.futures.ProcessPoolExecutor クラスの使用
concurrent.futures
モジュールは、より高レベルなインターフェースを提供しており、より簡単に並列処理を実装できます。その中の ProcessPoolExecutor
クラスは、複数のプロセスをプールとして管理し、タスクを効率的に実行できます。
コード例
from concurrent.futures import ProcessPoolExecutor
def square(num):
"""引数の平方を計算する関数"""
return num * num
if __name__ == "__main__":
# プロセスプール作成 (最大プロセス数は自動設定)
with ProcessPoolExecutor() as executor:
# タスクの実行
future = executor.submit(square, 5) # future オブジェクトを取得
# 親プロセス側で何か処理をする
print("親プロセス: 終了を待機せずに処理を続ける")
# 結果の取得
result = future.result()
print("計算結果:", result)
高度なライブラリを利用する
より高度な並列処理ライブラリ (Dask, Ray など) を利用することで、より複雑なタスクや分散コンピューティングに対応できます。