並行処理(Concurrency)とは?
並行処理とは、コンピュータが複数の作業を同時に実行することです。例えば、ゲームをしながら音楽を聴くことができるのは、コンピュータが同時に複数の処理をこなしているからです。
マルチスレッドとマルチプロセスの違い
- マルチスレッド: 1つのプログラム内で複数のスレッドが同時に動きます。スレッドはメモリを共有し、データをやり取りしやすいですが、1つのCPUしか使えません。
- マルチプロセス: プログラムを複数のプロセスに分けて並列に実行します。プロセスは独立して動き、複数のCPUを利用できるため、より多くの計算を同時に行えます。ただし、プロセス間でのデータのやり取りはスレッドより難しくなります。
from concurrent.futures
を使う文法
concurrent.futures
モジュールは、スレッド(ThreadPoolExecutor
)とプロセス(ProcessPoolExecutor
)の両方を使って並行処理や並列処理を簡単に行うことができます。submit()
や map()
メソッドを使うことで、関数を並行に実行し、結果を効率的に収集できます。
import multiprocessing
との違い
-
import multiprocessing
は低レベルなプロセス管理を行いますが、concurrent.futures
は高レベルなインターフェースで、使いやすく設計されています。 -
concurrent.futures
では、スレッドとプロセスの両方の管理が統一された文法で行えるため、コードの可読性が向上します。
例題 1: マルチスレッドを使った並行処理
まず、ThreadPoolExecutor
を使った並行処理の例を見てみましょう。
from concurrent.futures import ThreadPoolExecutor
import time
def func_1(x):
for n in range(3):
time.sleep(1)
print(f"func_1 - {n} ({x})")
return f"result - {x}"
def main():
print("start")
# スレッドプールを作成し、最大4つのスレッドで並行処理を実行
with ThreadPoolExecutor(max_workers=4) as executor:
results = executor.map(func_1, ["A", "B", "C", "D"])
print(list(results))
print("end")
if __name__ == "__main__":
main()
解説
-
ThreadPoolExecutor(max_workers=4)
は、4つのスレッドを同時に作成し、それぞれがfunc_1
を並行に実行します。 -
executor.map(func_1, ["A", "B", "C", "D"])
は、リスト内の要素をfunc_1
に渡し、各スレッドで実行します。 -
map()
の結果はリストにまとめられます。
例題 2: マルチプロセスを使った並列処理
次に、ProcessPoolExecutor
を使った並列処理の例を見てみましょう。
from concurrent.futures import ProcessPoolExecutor
import time
def func_2(x, y):
for n in range(3):
time.sleep(1)
print(f"func_2 - {n} ({x}, {y})")
return f"result - ({x}, {y})"
def main():
print("start")
# プロセスプールを作成し、最大2つのプロセスで並列処理を実行
with ProcessPoolExecutor(max_workers=2) as executor:
results = executor.map(func_2, ["A", "B"], ["X", "Y"])
print(list(results))
print("end")
if __name__ == "__main__":
main()
解説
-
ProcessPoolExecutor(max_workers=2)
は、最大2つのプロセスを同時に作成します。 -
executor.map(func_2, ["A", "B"], ["X", "Y"])
は、func_2
に ["A", "B"] と ["X", "Y"] の引数を渡し、並列に実行します。 - 各プロセスが独立して動作するため、複数のCPUコアを使って処理が行われます。
まとめ: 並行処理と並列処理の統一された使い方
-
concurrent.futures
を使うことで、並行処理と並列処理を同じ構造で扱えます。 -
submit()
とmap()
を使えば、どちらの方法でも効率的にタスクを実行できます。 - スレッドとプロセスを使い分けることで、プログラムの実行速度や効率を大きく向上させることができます。
参考資料
並列処理の基本を解説!マルチスレッド・マルチプロセスをconcurrent futuresで実装!
https://youtu.be/et-cDFbVkQw?si=byW5UavN_Go-lKz0