マルチプロセスとは?マルチスレッドとの違いとPythonでの使い分け(実測つき)

マルチプロセスとは、1つの仕事を複数の「プロセス」(それぞれ専用のメモリを持つ実行中のプログラム)に分けて同時に動かす方式です。マルチスレッドが1つのプロセスの中でメモリを共有しながら処理を分けるのに対し、マルチプロセスはメモリが分かれているため、1つが落ちても他に影響しにくく、PythonではCPUの計算を本当に並列で速くできます。

この記事では、プロセスとスレッドの違いを身近なソフトウェアの例で整理したうえで、Pythonのmultiprocessingで実際に測った結果(Apple M2・8コア、Python 3.12.15、2026年10月18日実行)を使って、どちらを選べばいいかを説明します。

プロセスとスレッドの違い

プログラムを起動すると、OSはそのプログラム専用のメモリ領域を用意して「プロセス」として動かします。スレッドはプロセスの中で動く処理の流れで、1つのプロセスの中に複数持てます。

比較 マルチプロセス マルチスレッド
メモリ プロセスごとに別々 プロセスの中で共有
データの受け渡し QueueやPipeで送る(コピーされる) 同じ変数をそのまま読み書きできる
1つが異常終了したとき 他のプロセスは動き続けやすい プロセスごと全体が止まる
起動の重さ 重い(下の実測で約26ミリ秒) 軽い(同じく約0.04ミリ秒)
Pythonで速くなる処理 CPUを使う計算 通信やファイルの待ち時間が長い処理
Pythonのモジュール multiprocessing、ProcessPoolExecutor threading、ThreadPoolExecutor

身近な例:ブラウザとデータベース

マルチプロセスはPythonだけの話ではありません。Google Chromeの土台であるChromiumは、1つのWebページの不具合でブラウザ全体が落ちないように、複数のプロセスに分けて動かす設計を採っています(Chromium「Multi-process Architecture」)。データベースのPostgreSQLも、接続ごとに専用のサーバープロセスを起動する「process per user」方式です(PostgreSQL公式ドキュメント)。どちらも2026年10月18日に確認しました。メモリが分かれていることを「安全性・頑丈さ」のために使っている例です。

「マルチプロセッシング」との違い

「マルチプロセッシング」は、複数のCPU(コア)を同時に使って処理することを指す言葉としても使われます。マルチプロセスはソフトウェアの分け方、マルチプロセッシングはCPUを複数使うことと考えると整理しやすいでしょう。Pythonのmultiprocessingモジュールは、複数のプロセスを作ることで複数のコアを使えるようにするモジュールです。

Pythonで実測:計算はプロセスでないと速くならない

同じ計算を4回行う処理を、順番に・スレッド4本・プロセス4つの3通りで比べます。

import os, time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def count(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

if __name__ == "__main__":
    jobs = [5_000_000] * 4
    t = time.perf_counter(); [count(n) for n in jobs]
    print(f"1つずつ順番に : {time.perf_counter()-t:.2f}秒")
    t = time.perf_counter()
    with ThreadPoolExecutor(4) as ex: list(ex.map(count, jobs))
    print(f"スレッド4本   : {time.perf_counter()-t:.2f}秒")
    t = time.perf_counter()
    with ProcessPoolExecutor(4) as ex: list(ex.map(count, jobs))
    print(f"プロセス4つ   : {time.perf_counter()-t:.2f}秒")

実行結果:

1つずつ順番に : 0.75秒
スレッド4本   : 0.76秒
プロセス4つ   : 0.23秒

スレッドでは順番に実行したときとほぼ同じ時間で、プロセスでは約3分の1になりました。通常のPython(CPython)にはGIL(グローバルインタープリタロック)があり、Pythonのコードを同時に実行できるスレッドは1本だけだからです。プロセスはそれぞれが別のPythonなので、GILの制約を受けません。公式ドキュメントも、multiprocessingはスレッドの代わりにサブプロセスを使うことでGILを回避すると説明しています(multiprocessing — Python公式ドキュメント、2026年10月18日確認)。GILの詳しい仕組みはPythonのGILとはで解説しています。

メモリが分かれていることを確かめる

import multiprocessing as mp, threading

counter = 0
def add():
    global counter
    counter += 1
    print("子の中の counter:", counter)

if __name__ == "__main__":
    print("開始方式:", mp.get_start_method())
    p = mp.Process(target=add); p.start(); p.join()
    print("プロセス後の親の counter:", counter)
    t = threading.Thread(target=add); t.start(); t.join()
    print("スレッド後の counter:", counter)

実行結果:

開始方式: spawn
子の中の counter: 1
プロセス後の親の counter: 0
子の中の counter: 1
スレッド後の counter: 1

子プロセスの中ではcounterが1になっていますが、親のcounterは0のままです。子プロセスは親のメモリのコピー(またはゼロから起動したPython)で動いているため、変更は親に伝わりません。スレッドでは同じ変数を書き換えるので、親からも1に見えます。プロセス間で値を受け渡す方法はプロセス間の変数を共有する方法にまとめています。

起動の重さを測る

import multiprocessing as mp, threading, time
def nothing(): pass
if __name__ == "__main__":
    N = 20
    t = time.perf_counter()
    for _ in range(N):
        th = threading.Thread(target=nothing); th.start(); th.join()
    ts = (time.perf_counter()-t)/N
    t = time.perf_counter()
    for _ in range(N):
        p = mp.Process(target=nothing); p.start(); p.join()
    ps = (time.perf_counter()-t)/N
    print(f"スレッド1本の起動と終了 : {ts*1000:.3f}ミリ秒")
    print(f"プロセス1つの起動と終了 : {ps*1000:.1f}ミリ秒")
    print(f"およそ {ps/ts:.0f} 倍")

実行結果:

スレッド1本の起動と終了 : 0.042ミリ秒
プロセス1つの起動と終了 : 26.1ミリ秒
およそ 627 倍

この環境では、プロセス1つの起動と終了にスレッドの数百倍の時間がかかりました。数ミリ秒で終わる小さな処理を1件ずつプロセスに投げると、かえって遅くなります。PoolやProcessPoolExecutorでプロセスを使い回し、ある程度まとまった量の計算を渡すのが基本です(ProcessPoolExecutorの使い方)。

Pythonのマルチプロセスでつまずく3つの点

1. if __name__ == “__main__”: が必要

WindowsとmacOSでは、子プロセスは新しいPythonを起動してスクリプトを読み込み直す「spawn」方式で作られます(上の実行結果の「開始方式: spawn」)。プロセスを作るコードをif __name__ == "__main__":の中に入れないと、子プロセスがさらに子プロセスを作ろうとしてエラーになります。理由はif __name__ == “__main__”: の意味で詳しく説明しています。

2. 渡せるのはpickleできるものだけ

from multiprocessing import Pool
if __name__ == "__main__":
    with Pool(2) as pool:
        try:
            pool.map(lambda x: x * 2, [1, 2, 3])
        except Exception as e:
            print(type(e).__name__, ":", e)

実行結果:

PicklingError : Can't pickle <function <lambda> at 0x10aeeeac0>: attribute lookup <lambda> on __main__ failed

プロセス間ではデータをpickle(バイト列への変換)して送るため、lambdaやその場で定義した関数は渡せません。defでモジュールの直下に定義した関数を渡します(エラーメッセージ中のアドレスは実行ごとに変わります)。

3. Python 3.14で既定の開始方式が変わった

公式ドキュメントによると、Linuxなどの POSIX 環境の既定の開始方式は、Python 3.14でforkからforkserverに変わりました。macOSは3.8から、Windowsは以前からspawnが既定です。forkを前提に書いた古いコード(親のグローバル変数を子でそのまま使うなど)は、3.14以降のLinuxで動きが変わることがあります。開始方式の指定方法はmultiprocessingの関数一覧(set_start_method)を参照してください。

マルチプロセスとマルチスレッドの使い分け

処理の種類 例 向いている方式
CPUで計算し続ける 画像の変換、大量の数値計算、データの集計 マルチプロセス
待ち時間が長い Webページの取得、APIの呼び出し、ファイルのダウンロード マルチスレッド(またはasyncio)
1件が数ミリ秒で終わる小さな処理 短い文字列の変換を少量 分けずに順番に実行

待ち時間が中心の処理でスレッドが速くなる様子はマルチスレッドとはで実測しています。Process・Pool・Queueなどの具体的な書き方はmultiprocessingの使い方をご覧ください。

よくある質問

Q. マルチプロセスとマルチスレッドの違いは?

マルチプロセスはメモリが別々のプロセスに処理を分け、マルチスレッドは1つのプロセスの中でメモリを共有しながら処理を分けます。Pythonでは、計算はマルチプロセス、通信などの待ちが多い処理はマルチスレッドが向いています。

Q. Pythonでマルチスレッドにしても計算が速くならないのはなぜ?

通常のPythonにはGILがあり、Pythonのコードを同時に実行できるスレッドが1本に限られるためです。計算を並列にするにはmultiprocessingやProcessPoolExecutorでプロセスを分けます。

Q. マルチプロセスのデメリットは?

起動が重いこと、メモリを多く使うこと、プロセス間でデータを受け渡すにはQueueなどで送る必要があることです。渡せるのはpickleできるデータだけです。

Q. マルチプロセッシングとマルチプロセスは同じ意味?

近い言葉ですが、マルチプロセッシングは複数のCPUを使って処理すること、マルチプロセスはソフトウェアを複数のプロセスに分けることを指して使われることが多いです。Pythonのmultiprocessingモジュールは、プロセスを分けて複数のCPUを使うためのものです。

まとめ

  • マルチプロセスは、メモリが別々のプロセスに処理を分ける方式
  • メモリが分かれているので頑丈。ChromiumやPostgreSQLもこの設計を使っている
  • Pythonでは計算を本当に並列にできる(実測で約3分の1)
  • 起動は重い(実測でスレッドの数百倍)ので、Poolで使い回す
  • if __name__ == "__main__":とpickleの制約に注意

あわせて読みたい

対面・無料|東京・永田町で毎週土曜日13時

1回の参加で、プログラミングの構造がわかる

ゼロからはじめるPython入門講座

8年間・累計600回開催/これまで全国で2万人以上が参加

「やはり対面で聞けるのはとても気づきがあり、価値があると思います」

参加者の声

  • 本当に初心者からの講座です。プログラミングに触ったことがなくて大丈夫です
  • 対面だから、その場で聞けます。つまずいた所を持ち帰らずに解決できます
  • 講座は1時間。そのあとも残って学習を続けられ、講師にも質問できます
  • 独学で挫折した方も。糸口が見つかって、講座のあと会員になる方が多くいます

無料で参加する|開催日程を見る

参加費は無料です