はじめに
Python は使いやすく強力なプログラミング言語ですが、並列処理を実装する際には GIL(Global Interpreter Lock) という制約があり、適切な手法を選択することが重要です。本記事では、Python における並列処理の主要な手法である スレッド(threading)、プロセス(multiprocessing)、非同期処理(asyncio) について詳しく解説します。
本記事を読むことで、Python における並列処理の仕組みを理解し、適切な手法を選択できるようになります。
1. Python における並列処理の基本概念
並列処理を理解するためには、以下の2つの概念を区別することが重要です。
- 並行処理(Concurrency):複数のタスクを「同時に進める」ように見せる技術。実際には 1 つの CPU コアがタスクを切り替えて処理している。
- 並列処理(Parallelism):複数のタスクを「完全に同時に実行する」技術。複数の CPU コアを使用して同時に処理を行う。
Python には、並行処理と並列処理を実現するためのいくつかの手法があります。
手法 | 並行処理 or 並列処理 | CPUバウンド | I/Oバウンド |
---|---|---|---|
threading | 並行処理 | ❌ 適さない | ✅ 適している |
multiprocessing | 並列処理 | ✅ 最適 | ❌ 適さない |
asyncio | 並行処理 | ❌ 不適 | ✅ 最適 |
2. threading モジュール(スレッド並列処理)
特徴
- Python の
threading
モジュールを使用すると、複数のスレッドを作成して並行に実行できる。 - ただし GIL により 1 つのスレッドしか同時に動作できない。
- I/O バウンド(ファイル操作、ネットワーク通信など)に適している。
実装例(ファイルダウンロードの並列化)
import threading
import time
def download_file(url):
print(f"{url} のダウンロード開始")
time.sleep(2)
print(f"{url} のダウンロード完了")
urls = ["https://example.com/file1", "https://example.com/file2"]
threads = []
for url in urls:
t = threading.Thread(target=download_file, args=(url,))
t.start()
threads.append(t)
for t in threads:
t.join()
print("すべてのダウンロード完了")
メリットとデメリット
✅ I/O バウンドの処理に適している
✅ multiprocessing
よりもメモリ消費が少ない
❌ GIL の影響を受けるため、CPU バウンドの処理には向かない
❌ スレッド間のデータ競合やデッドロックのリスクがある
3. multiprocessing モジュール(プロセス並列処理)
特徴
multiprocessing
は プロセス を作成し、GIL の制約を受けずに並列処理を実現。- CPU バウンド(計算負荷が高い処理)に最適。
- プロセス間でメモリを共有しないため、データの受け渡しには
Queue
やManager
が必要。
実装例(CPU負荷の高い計算)
import multiprocessing
import time
def heavy_computation(n):
print(f"計算開始: {n}")
result = sum(i * i for i in range(n)) # 重い計算
print(f"計算完了: {n}, 結果: {result}")
if __name__ == "__main__":
numbers = [10**7, 10**7]
processes = []
for n in numbers:
p = multiprocessing.Process(target=heavy_computation, args=(n,))
p.start()
processes.append(p)
for p in processes:
p.join()
print("すべての計算が完了しました")
メリットとデメリット
✅ GIL の影響を受けないため CPU をフル活用できる
✅ 機械学習、データ分析、画像処理などに最適
❌ プロセス作成のオーバーヘッドが大きい
❌ メモリ消費が多く、大量のプロセスを作るとメモリを圧迫
4. asyncio モジュール(非同期処理)
特徴
asyncio
はイベントループを使用し、スレッドやプロセスを作成せずに 非同期処理 を実現。- ネットワーク通信、ファイル操作、データベースアクセスなどの I/O バウンド処理 に最適。
実装例(非同期のAPIリクエスト)
import asyncio
async def fetch_data(url):
print(f"{url} のデータ取得開始")
await asyncio.sleep(2)
print(f"{url} のデータ取得完了")
async def main():
urls = ["https://example.com/data1", "https://example.com/data2"]
await asyncio.gather(*(fetch_data(url) for url in urls))
asyncio.run(main())
メリットとデメリット
✅ スレッドやプロセスを作らず軽量で効率的
✅ ネットワーク通信やファイル操作などに最適
❌ CPU バウンドの処理には向かない
❌ async
に対応していないライブラリとの互換性が低い
5. まとめ
手法 | 並行処理 or 並列処理 | CPUバウンド | I/Oバウンド |
threading | 並行処理 | ❌ | ✅ |
multiprocessing | 並列処理 | ✅ | ❌ |
asyncio | 並行処理 | ❌ | ✅ |
どの手法を選ぶべきか?
- CPU負荷の高い処理(機械学習、データ分析) →
multiprocessing
- ネットワーク通信、データベース処理 →
asyncio
- ファイル操作、I/O 待ちが多い処理 →
threading
Python で最適な並列処理を選び、パフォーマンスを最大化しましょう!