Python では、マルチスレッドとマルチプロセスが一般的な並行プログラミングの方法です。両方ともプログラムの実行効率を向上させることができますが、実装原理は大きく異なります。この記事では、Python のマルチスレッドとマルチプロセスの違いについて詳しく説明し、なぜマルチスレッドが「偽のマルチスレッド」と呼ばれるのかを説明します。私も Python を初めて学び始めた時に、データ処理中にマルチスレッドの効率が単一スレッドよりも低いことに気付いたことから、さらに理解を深めました。
マルチスレッドとマルチプロセスの違い#
マルチスレッドとマルチプロセスは並行プログラミングの方法ですが、実装方法は大きく異なります。マルチプロセスは、オペレーティングシステムで同時に複数のプロセスを実行することを意味します。各プロセスは独自のアドレス空間とシステムリソースを持っています。マルチスレッドは、同じプロセス内で複数のスレッドを同時に実行することを意味します。各スレッドは同じアドレス空間とシステムリソースを共有します。
具体的には、マルチプロセスは新しいプロセスを作成して並行処理を実現します。各プロセスは独自のアドレス空間とシステムリソースを持ち、プロセス間通信(IPC)メカニズムを使用して通信します。マルチスレッドは、新しいスレッドを作成して並行処理を実現します。すべてのスレッドは同じアドレス空間とシステムリソースを共有し、共有メモリと同期メカニズムを使用して通信します。
マルチプロセスはオペレーティングシステムレベルで実装されており、各プロセスが独自のアドレス空間とシステムリソースを持つため、安定性とセキュリティが向上します。ただし、プロセスの作成と破棄には多くのシステムリソースが必要なため、マルチプロセスは CPU 集中型のタスクに適しています。一方、マルチスレッドは IO 集中型のタスクに適しています。なぜなら、スレッド間の切り替えのコストがプロセス間の切り替えのコストよりもはるかに小さいからです。
なぜマルチスレッドは「偽のマルチスレッド」なのか#
Python では、GIL(Global Interpreter Lock)メカニズムの存在により、マルチスレッドは真の並行性を実現することができません。GIL は Python インタプリタ内のロックであり、同時に実行できるのは 1 つのスレッドだけです。つまり、いつでも 1 つのスレッドしか Python コードを実行できないということです。マルチスレッドはメモリ内に同時に存在できるものの、Python コードを真に並行して実行することはできません。そのため、Python ではマルチスレッドを使用しても CPU 集中型のタスクの実行効率は向上しません。
ただし、IO 集中型のタスクには、Python のマルチスレッドは依然として利点があります。なぜなら、IO 集中型のタスクでは、ほとんどの時間が IO 操作の完了を待つことに費やされるからです。この場合、GIL メカニズムはプログラムの実行効率にほとんど影響を与えません。
マルチスレッドとマルチプロセスの開始方法#
Python では、マルチスレッドやマルチプロセスを開始するためにさまざまな方法があります。以下ではそれぞれの利点と欠点について説明します。
マルチスレッドの開始方法#
1. threading モジュールの使用#
import threading
def worker():
print("I'm working")
t = threading.Thread(target=worker)
t.start()
threading モジュールを使用して新しいスレッドを作成するのは非常に簡単です。Thread オブジェクトを作成し、実行する関数を引数として渡すだけです。ただし、GIL メカニズムの存在により、マルチスレッドを使用して真の並行実行を実現することはできません。
2. concurrent.futures モジュールの使用#
from concurrent.futures import ThreadPoolExecutor
def worker():
print("I'm working")
with ThreadPoolExecutor() as executor:
executor.submit(worker)
concurrent.futures モジュールを使用すると、より簡単に新しいスレッドを作成できます。さらに、ThreadPoolExecutor クラスを使用してスレッドプールを管理することもできます。ただし、GIL メカニズムの存在により、マルチスレッドを使用して真の並行実行を実現することはできません。
マルチプロセスの開始方法#
1. multiprocessing モジュールの使用#
import multiprocessing
def worker():
print("I'm working")
p = multiprocessing.Process(target=worker)
p.start()
multiprocessing モジュールを使用して新しいプロセスを作成するのは非常に簡単です。Process オブジェクトを作成し、実行する関数を引数として渡すだけです。各プロセスは独自のアドレス空間とシステムリソースを持つため、真の並行実行を実現することができます。
2. concurrent.futures モジュールの使用#
from concurrent.futures import ProcessPoolExecutor
def worker():
print("I'm working")
with ProcessPoolExecutor() as executor:
executor.submit(worker)
concurrent.futures モジュールを使用すると、より簡単に新しいプロセスを作成できます。さらに、ProcessPoolExecutor クラスを使用してプロセスプールを管理することもできます。各プロセスは独自のアドレス空間とシステムリソースを持つため、真の並行実行を実現することができます。
コードの例#
以下に、マルチプロセスを使用して円周率を並列計算する例を示します。
import multiprocessing
import time
def calc_pi(digits):
start = time.time()
pi = 0
for k in range(digits):
pi += ((-1) ** k) / (2 * k + 1)
pi *= 4
end = time.time()
print(f"Digits: {digits}, Time: {end - start:.4f}s")
return pi
if __name__ == '__main__':
with multiprocessing.Pool() as pool:
digits_list = [100000, 200000, 300000, 400000, 500000]
results = pool.map(calc_pi, digits_list)
for digits, pi in zip(digits_list, results):
print(f"Digits: {digits}, Pi: {pi:.10f}")
この例では、multiprocessing.Pool クラスを使用してプロセスプールを作成し、digits_list の各要素に対して calc_pi () 関数を呼び出します。各要素は異なるプロセスで計算されるため、真の並列計算が実現されます。
まとめ#
本記事では、Python のマルチスレッドとマルチプロセスの違いについて詳しく説明し、なぜマルチスレッドが「偽のマルチスレッド」と呼ばれるのかを説明しました。また、マルチスレッドやマルチプロセスを開始するためのさまざまな方法を紹介し、コードの例も示しました。実際の開発では、タスクの種類に応じて適切な並行プログラミングの方法を選択し、プログラムの実行効率を向上させることが重要です。
20230712 更新#
今日のニュースによると、Meta 社が GIL を削除するために 3 人のエンジニア年を費やすと約束し、現在は Python コミュニティが PEP703 の提案を受け入れるのを待っているとのことです。ソース