【Python】マルチプロセスとは?初心者向けに並列処理の仕組みと活用方法をわかりやすく解説

はじめに

Pythonでプログラムを効率的に動かすうえで、マルチプロセスという手法を耳にしたことがあるかもしれません。
これは、複数のプロセスを同時に実行することで、CPUにかかる負荷を上手に分散させる仕組みです。

たとえば大量のデータを扱う処理や、画像・動画を扱う複雑な演算などを扱う際に、とても有用です。
マルチプロセスは、単純に1つの処理を延々と繰り返すやり方よりも効率よくCPUを活用できます。

ですが、初めてこの仕組みを使おうとすると、専門用語や概念が多くて少し身構えてしまうこともあるのではないでしょうか。
そこでこの記事では、Python マルチプロセスの基本的な考え方や、実務に役立つ活用例、コード例などをまとめてご紹介します。

この記事を読むとわかること

  • マルチプロセスとはどんな仕組みか
  • マルチスレッドとの違い
  • Pythonにおけるmultiprocessingモジュールの使い方
  • 実務での具体的な活用シーン
  • マルチプロセスを運用する際の注意点

マルチプロセスとは

マルチプロセスとは、1つのプログラムを複数の独立したプロセスに分割して同時に実行させる仕組みです。
プロセスというのは、簡単に言うとOS(オペレーティングシステム)によって管理されるプログラムの実行単位です。

通常、Pythonプログラムを単一のプロセスで動かしていると、すべての処理を1つの流れで実行します。
しかし、処理の内容によってはCPUに余力があるのに全体としては時間がかかってしまう場合もあります。

そこで複数のプロセスに分けて同時進行させれば、パソコンがもつ複数コアを活かして処理を効率化できるというわけです。
とくに数値演算やデータ変換などCPU負荷の重いタスクでは、このマルチプロセスが効果を発揮します。

マルチスレッドとの違い

並列実行というとマルチスレッドと呼ばれる方法もあります。
スレッドはプロセスの中で走る処理の単位で、1つのプロセスの中に複数のスレッドを立ち上げる形です。

Pythonには GIL (Global Interpreter Lock)と呼ばれる仕組みがあり、あるタイミングで実行できるスレッドが1つに制限されてしまうという特徴があります。
そのため、Pythonでマルチスレッドを使ったとしても、CPUを100%並列に使うことが難しくなる場合があります。

一方、マルチプロセスはプロセス自体が別物としてOSに管理されるので、GILの影響を受けにくいです。
結果として、CPUパワーをフル活用しやすいというメリットがあります。
CPUリソースを最大限に使いたい場合は、マルチスレッドよりもマルチプロセスが有利だと言われることが多いです。

Python マルチプロセスの基本的な使い方

multiprocessingモジュールを使う

Pythonでマルチプロセスを実現するには、標準ライブラリとして用意されているmultiprocessingモジュールを使うことが多いです。
このモジュールには以下のようなクラスや機能が含まれています。

  • Process:プロセスを生成して実行するクラス
  • Pool:プロセスのプールを用意して、並列実行するタスクを管理
  • Queue:プロセス間通信で用いるキュー
  • Lock:プロセス間で競合状態を回避するための仕組み

ここでは最も基本的なProcessを使ったシンプルな例を見てみましょう。

from multiprocessing import Process
import time

def worker(name):
    print(f"開始: {name}")
    time.sleep(1)
    print(f"終了: {name}")

if __name__ == '__main__':
    # プロセスを3つ生成
    processes = []
    for i in range(3):
        p = Process(target=worker, args=(f"タスク{i}",))
        processes.append(p)
        p.start()

    # プロセスの終了を待機
    for p in processes:
        p.join()

    print("すべてのタスクが完了しました。")

Processクラスを使う場合は、targetとして実行したい関数を指定し、argsで引数を渡します。
上記では3つのプロセスが同時並行で動くため、タスクごとの待ち時間が重複せずに進み、トータルの処理時間を短縮できます。

Poolを使った並列実行

複数の処理を簡単にまとめて並列実行したいときは、Poolを使う方法も便利です。
以下のようにすると、リストなどに格納した複数の値に対して並列処理を一括で適用できます。

from multiprocessing import Pool

def square_number(x):
    return x * x

if __name__ == '__main__':
    data = [1, 2, 3, 4, 5]
    with Pool(processes=3) as pool:
        result = pool.map(square_number, data)
    print(result)  # [1, 4, 9, 16, 25]

Pool(processes=3) とすると、3つのプロセスをプールに用意してくれます。
pool.map() を使うことで、指定した関数をリストの各要素に並列適用し、その結果をリストとして返してくれます。

メリットと注意点

CPUを有効活用できる

マルチプロセスの最大のメリットは、CPUを効率的に使えることです。
特にCPU使用率が高い計算タスクをスピードアップさせることが期待できます。
シングルプロセスでは1コアしか使えなかった処理が、マルチプロセスによって複数コアを同時に使えるようになります。

オーバーヘッドがある

ただし、プロセスを立ち上げること自体にオーバーヘッド(余計な処理負荷)が生じます。
同じパソコンであれば、プロセスの数が増えすぎるとかえって遅くなる場合もあります。
実務で最適なプロセス数を決めるときは、CPUコア数やメモリ消費などを考慮することが大切です。

メモリ領域が独立する

マルチスレッドと違って、マルチプロセスでは各プロセスが独立したメモリ領域を持ちます。
そのため、複数のプロセス間でデータのやり取りをするには少し工夫が必要です。
同時に、スレッドよりもプロセスのほうがクラッシュ時に影響が広がりにくいという利点もあります。

メインプロセスが終了しても、サブプロセスが残ってしまうケースがあります。終了管理が適切にできていないと、知らないうちにメモリを使い続けてしまうこともあるので注意してください。

実務での活用例

データ処理

ビッグデータを扱うようなプロジェクトで、データを集計・変換・分析するといった作業が大量に必要になることがあります。
こうした場合、データを複数の塊に分割してマルチプロセスで並列処理すれば、単一プロセスで処理するよりも時間を短縮しやすいです。
特に機械学習の前処理や、大量のログファイルから情報を抽出するといった場面では有効でしょう。

画像や動画の処理

画像をフィルタリングしたり、動画から特定のフレームを切り出したりといった操作も、1個ずつ順番に処理すると時間がかかります。
しかし、複数の画像や動画を並列に扱うことで処理の待ち時間を削減できます。
一定のまとまったデータ量があれば、マルチプロセスを使う価値があります。

Webスクレイピング

Webサイトから情報を取得してデータを蓄積する際、同時に複数のサイトへアクセスして情報収集することも考えられます。
このようにネットワーク待ちが発生するタスクを分散する場合でも、マルチプロセスによる並列化は有効な手段の1つです。
ただし、対象サイトのサーバーに負荷をかけすぎないようにするなどの配慮は必要になります。

プロセス間通信(IPC)の活用

マルチプロセスで並列実行していると、プロセス同士でデータを受け渡したい場面が出てきます。
PythonではQueuePipeManagerなどが用意されており、これらを使うとプロセス間通信(IPC)が実現できます。

Queueを使った例

from multiprocessing import Process, Queue

def worker(q, data):
    # 受け取ったデータを加工してキューに送る
    result = data.upper()
    q.put(result)

if __name__ == '__main__':
    q = Queue()
    p = Process(target=worker, args=(q, "hello"))
    p.start()

    # キューから結果を受け取る
    processed_data = q.get()
    p.join()

    print(processed_data)  # "HELLO"

上の例では、Queueオブジェクトを使ってプロセス間でデータを送受信しています。
q.put() でデータを送信し、q.get() で受信するだけなので、コードとしては比較的シンプルです。

LockやManagerを使った例

同時アクセスを制御するために、LockManagerを使うこともあります。
複数プロセスが同じデータへ同時に書き込んでしまうと、処理が競合する恐れがあるからです。

from multiprocessing import Process, Manager, Lock

def increment(shared_dict, lock):
    with lock:
        shared_dict["count"] += 1

if __name__ == '__main__':
    with Manager() as manager:
        lock = Lock()
        shared_dict = manager.dict()
        shared_dict["count"] = 0

        processes = [Process(target=increment, args=(shared_dict, lock)) for _ in range(5)]
        for p in processes:
            p.start()
        for p in processes:
            p.join()

        print(shared_dict["count"])  # 5

Manager().dict() を使うと、プロセス間で共有できる辞書を作成できます。
さらにLockを利用して、同じデータにアクセスするタイミングが重なるのを防いでいます。
こうすることで、予期しない上書きが起きず、正しい結果を得られるわけです。

よくあるトラブルシューティング

if name == 'main' の重要性

Windowsなどの一部環境では、Processを生成するコードはif __name__ == '__main__':の中に書かないとエラーが起きる場合があります。
これは、プロセスを生成するときにコードを複製する仕組みに関係しており、無限にプロセスが立ち上がらないようにするための措置です。

大量のプロセスを一度に作らない

たくさんのプロセスを作りすぎると、逆にパフォーマンスが下がることがあります。
CPUコア数やメモリの状況を考えながら、適切な数のプロセスを生成するとよいでしょう。

一部のライブラリでは、プロセス間で使えない機能や、マルチプロセスと相性が悪い仕組みが含まれていることがあります。ライブラリのドキュメントを確認するときは、その点もチェックしてみてください。

まとめ

Python マルチプロセスは、複数のプロセスを同時並行で動かす仕組みです。
CPUのコアをフル活用できるので、CPU負荷の高い処理を効率よくこなせるのがメリットといえます。

一方でプロセス間通信やロック、あるいはプロセス数によるオーバーヘッドなど、注意すべきポイントも存在します。
しかし、基本を理解して適切に設定すれば、大量のデータ処理や画像・動画の変換など、時間のかかる処理を短縮できる可能性が高いでしょう。

今回ご紹介したmultiprocessingモジュールの仕組みやコード例をヒントに、ぜひマルチプロセスのメリットを活かしてみてください。
複数のプロセスが協力して処理を分担するイメージを持つと、並列化の実感が湧きやすいはずです。
シングルプロセスで詰まっていた場面でも、マルチプロセスを導入すればパフォーマンス向上が期待できます。

Pythonをマスターしよう

この記事で学んだPythonの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。