Python での並列処理: multiprocessing.Process と concurrent.futures の比較

2025-03-21

Python の multiprocessing.Process.run() メソッドについてですが、実は 直接呼び出すことは intended (想定) されません。ちょっと紛らわしいですね。

multiprocessing.Process クラス は、別の Python プロセスを起動するための仕組みを提供します。このプロセスは、親プロセス (メインプログラム) とは独立に、別々のメモリ空間 で実行されます。

run() メソッド は、この新しく作成されたプロセス内で実行される関数です。しかし、直接 process.run() を呼び出すと、親プロセスと同じスレッドで実行されてしまいます

プロセスを起動するには、以下の手順で行います。

  1. ターゲット関数 を定義します。これは、新しいプロセスで実行したい実際の処理を記述した関数です。
  2. Process クラスをインスタンス化します。このとき、 target 引数に先ほど定義したターゲット関数を渡します。
  3. start() メソッドを呼び出します。これにより、新しいプロセスが生成され、バックグラウンドで run() メソッドの実行が開始されます。
  4. 必要に応じて、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)と呼ばれる変換が必要です。しかし、シリアライズできないオブジェクト を関数引数として渡すとエラーが発生します。

対処法

  • シリアライズできないライブラリを使用している場合は、別途工夫が必要になる場合があります。
  • シリアライズできないオブジェクトの使用を避けるか、シリアライズ可能な形式に変換してから渡しましょう。

子プロセス側でのモジュールインポートエラー

親プロセスで読み込んだモジュールを、子プロセスでもそのまま利用できるわけではありません。子プロセスは独立したメモリ空間を持つため、モジュールを新たにインポートする必要があります。

対処法

  • 子プロセス内で使用するモジュールを明示的にインポートしましょう。

子プロセス側の標準出力の扱いに注意

子プロセスの標準出力は、そのまま親プロセス側に出力されません。子プロセスの出力を取得したい場合は、QueuePipe などを使って親プロセスに送る必要があります。



注意
multiprocessing.Process.run() メソッドは 直接呼び出すことは intended (想定) されません。新しいプロセスを起動するための仕組みを提供する multiprocessing.Process クラスの内部メソッドです。

実用的なコード例 としては、以下の手順でプロセスを起動する方法が一般的です。

  1. ターゲット関数 を定義します。
  2. Process クラスをインスタンス化し、 target 引数にターゲット関数を渡します。
  3. 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("親プロセス: 終了を待機せずに処理を続ける")

解説

  1. square 関数は、引数の平方を計算します。
  2. Process クラスをインスタンス化し、 target 引数に square 関数を、 args 引数にタプル (5,) を渡します。タプル (5,) は、関数に渡す引数を表し、ここでは 5 を渡します。
  3. start() メソッドを呼び出して、プロセスを起動します。
  • 子プロセスの標準出力を親プロセスで取得したい場合は、QueuePipe などを使って通信を行う必要があります。
  • 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 クラスを使ってプロセスを起動する方法です。以下の手順で行います。

  1. ターゲット関数 を定義します。これは、新しいプロセスで実行したい処理を記述した関数です。
  2. Process クラスをインスタンス化します。このとき、 target 引数に先ほど定義したターゲット関数を渡します。
  3. 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 など) を利用することで、より複雑なタスクや分散コンピューティングに対応できます。