Pythonで同時に2つ以上の処理をする方法を紹介します
- スレッド
- スレッドプール
- プロセスプール
- イベントループ(コルーチン)
スレッド (threading)
スレッドを使えば、複数の関数を同時に動かすことができます。
threading.Thread
クラスに target
として関数を渡し、start()
で開始すると動きます。
import time
import threading
def func1():
while True:
print("func1")
time.sleep(1)
def func2():
while True:
print("func2")
time.sleep(1)
if __name__ == "__main__":
thread_1 = threading.Thread(target=func1)
thread_2 = threading.Thread(target=func2)
thread_1.start()
thread_2.start()
実行結果
func1
func2
func2
func1
func1
func2
func2
func1
スレッドプール (concurrent.futures)
Python 3.2以降の、concurrent.futures パッケージ を使うとさらに強力です。
その中の ThreadPoolExecutor クラス を使います。
最初に同時に動かす最大数 max_workers
を決めるとスレッドを使いまわしてくれるので上で紹介した普通のスレッドよりかしこいです。
Pythonのバージョンが新しいならば、積極的に使っていいと思います。
import time
import concurrent.futures
def func1():
while True:
print("func1")
time.sleep(1)
def func2():
while True:
print("func2")
time.sleep(1)
if __name__ == "__main__":
executor = concurrent.futures.ThreadPoolExecutor(max_workers=2)
executor.submit(func1)
executor.submit(func2)
実行結果
func1
func2
func1
func2
func1
func2
func1
func2
プロセスプール (concurrent.futures)
上に同じく concurrent.futures パッケージ ですが、スレッドプールではなくプロセスプールというのもあります。
スレッドではなくプロセス単位に分けることで、Global Interpreter Lock (GIL) の制約を受けなくなりマルチコアで動かせるようになります。
ただし、その分スレッドよりも規模が大きいプロセスを使うので、他の制約が増えることも。注意!
使い方は簡単で、上で紹介した ThreadPoolExecutor
を ProcessPoolExecutor
に変えるだけです。
import time
import concurrent.futures
def func1():
while True:
print("func1")
time.sleep(1)
def func2():
while True:
print("func2")
time.sleep(1)
if __name__ == "__main__":
executor = concurrent.futures.ProcessPoolExecutor(max_workers=2)
executor.submit(func1)
executor.submit(func2)
実行結果
func1
func2
func1
func2
func1
func2
func1
func2
イベントループ(コルーチン)
ひとつのスレッドで複数の処理を動かす方法もあります。そのひとつがイベントループです。
Python3.4以降では asyncio モジュール で実現できます。
マルチスレッドとの違い、どのような場合に使うと良いか…といったお話は Pythonにおける非同期処理: asyncio逆引きリファレンス を読むとわかりやすいです。
通信やファイル入出力といった非同期I/Oあたりにはスレッドを増やすよりも非常に効率的なのですが、概念が難しいのでなれるまで大変です・・。
サンプルコードはスレッドの場合とは結構違います。
待機する処理に time.sleep
ではなく asyncio.sleep
を使っていますが、これは asyncio.sleep
を呼び出して待っている間に他の並列している処理に移動するためです。まさにコルーチンですね。
import asyncio
@asyncio.coroutine
def func1():
while True:
print("func1")
yield from asyncio.sleep(1)
@asyncio.coroutine
def func2():
while True:
print("func2")
yield from asyncio.sleep(1)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
tasks = asyncio.wait([func1(), func2()])
loop.run_until_complete(tasks)
実行結果
func2
func1
func2
func1
func2
func1
func2
func1