高校国語785655 views
世界の国560595 views
MathPython491378 views
数学講師2852771 views
中学社会667106 views
高校日本史189857 views
小学理科717236 views
中学数学621382 views
中学理科1626207 views
小学社会308636 views
Help
Tools

English

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

Python で並列処理を行う方法として、マルチスレッドとマルチプロセスがあります。それぞれに特徴があり、処理の内容によって使い分けが重要です。

基本的な違い

マルチスレッド

同じプロセス内でメモリを共有。GIL により同時に1スレッドだけが Python コードを実行。I/O バウンドな処理に適している。

マルチプロセス

独立したプロセスでメモリは分離。真の並列実行が可能。CPU バウンドな処理に適している。

GIL の影響

Python の GIL(Global Interpreter Lock)により、マルチスレッドでは CPU バウンドな処理の並列化ができません。

import threading
import multiprocessing
import time

def cpu_bound(n):
    """CPU を使う重い計算"""
    total = 0
    for i in range(n):
        total += i * i
    return total

N = 10_000_000

# シングルスレッド
start = time.time()
cpu_bound(N)
cpu_bound(N)
print(f"シングル: {time.time() - start:.2f}秒")

# マルチスレッド(GIL のため速くならない)
start = time.time()
t1 = threading.Thread(target=cpu_bound, args=(N,))
t2 = threading.Thread(target=cpu_bound, args=(N,))
t1.start(); t2.start()
t1.join(); t2.join()
print(f"マルチスレッド: {time.time() - start:.2f}秒")

# マルチプロセス(真の並列化)
start = time.time()
p1 = multiprocessing.Process(target=cpu_bound, args=(N,))
p2 = multiprocessing.Process(target=cpu_bound, args=(N,))
p1.start(); p2.start()
p1.join(); p2.join()
print(f"マルチプロセス: {time.time() - start:.2f}秒")

I/O バウンド処理にはスレッド

ファイル I/O やネットワーク通信など、待ち時間が多い処理にはスレッドが適しています。

from concurrent.futures import ThreadPoolExecutor
import urllib.request
import time

def download(url):
    response = urllib.request.urlopen(url)
    return len(response.read())

urls = ["https://example.com"] * 5

start = time.time()
with ThreadPoolExecutor(max_workers=5) as executor:
    results = list(executor.map(download, urls))
print(f"スレッド: {time.time() - start:.2f}秒")

I/O 待ち中は GIL が解放されるため、スレッドが効果的です。

CPU バウンド処理にはプロセス

数値計算や画像処理など、CPU を使う処理にはプロセスが適しています。

from concurrent.futures import ProcessPoolExecutor
import time

def compute(n):
    return sum(i * i for i in range(n))

numbers = [5_000_000] * 4

start = time.time()
with ProcessPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(compute, numbers))
print(f"プロセス: {time.time() - start:.2f}秒")
print(f"結果: {results}")

使い分けの判断基準

処理の種類推奨理由
ファイル読み書きスレッドI/O 待ち中に他のスレッドが動ける
ネットワーク通信スレッドI/O 待ちが多い
数値計算プロセスCPU を使うので真の並列化が必要
画像処理プロセスCPU バウンド
データ変換プロセスCPU バウンド

メモリ共有の違い

import threading
import multiprocessing

shared_list = []

def append_thread(item):
    shared_list.append(item)  # 同じリストを共有

def append_process(item):
    shared_list.append(item)  # 別プロセスなので反映されない

# スレッド
t = threading.Thread(target=append_thread, args=("thread",))
t.start(); t.join()
print(f"スレッド後: {shared_list}")  # ['thread']

# プロセス
p = multiprocessing.Process(target=append_process, args=("process",))
p.start(); p.join()
print(f"プロセス後: {shared_list}")  # ['thread'](変わらない)

プロセス間でデータを共有するには、QueueManager を使う必要があります。

作成コストの違い

スレッド

作成コストが低い。メモリ効率が良い。大量に作成しても問題ない。

プロセス

作成コストが高い。メモリを多く消費。作成数を抑えるべき。

組み合わせて使う

I/O と CPU の両方がある場合は、組み合わせることもあります。

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import urllib.request

def download(url):
    response = urllib.request.urlopen(url)
    return response.read()

def process_data(data):
    # CPU を使う処理
    return len(data) * 2

# ダウンロード(I/O)はスレッドで
with ThreadPoolExecutor(max_workers=5) as executor:
    downloaded = list(executor.map(download, ["https://example.com"] * 5))

# データ処理(CPU)はプロセスで
with ProcessPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(process_data, downloaded))

まとめ

スレッドを選ぶ場合

I/O バウンドな処理。軽量な並行処理。データ共有が多い場合。

プロセスを選ぶ場合

CPU バウンドな処理。真の並列化が必要。メモリ分離が必要。

処理の特性を理解して、適切な並列化手法を選びましょう。