[Python] 並列・並行・非同期処理を理解する
はじめに
Pythonの並列・並行・非同期処理を学んでいて混乱することがあったため、整理しました。
記事作成にあたり、Python周りの公式ドキュメント、動画および技術記事を参照いたしましたが、間違っている理解があればご指摘いただけると幸いです。
並列処理:parallelism
並列処理とは?
- 複数のワーカー(作業者)がタスクを同時並行で進める処理です。
- マルチプロセスによって、上記のような処理を実現しています。
- 例:VScodeを立ち上げると、VScodeのプロセスが立ち上がります。
- CPUリソースを大量に要する、
CPUバウンド処理に対して有効です。 - pythonでは、
multiprocessingやconcurrent.futureを使うことで実現
コードで学ぶ
以下は逐次処理のsleep_10()が完了してからsleep_5()が実行される。
import time
def sleep_10():
time.sleep(10)
print(f'sleep_10')
def sleep_5():
time.sleep(5)
print(f'sleep_5')
def main():
print(f"main開始 {time.strftime('%X')}")
sleep_10()
sleep_5()
print(f"main終了 {time.strftime('%X')}")
if __name__ == "__main__":
main()
"""
# 標準出力の結果は以下
main開始 18:35:27
sleep_10
sleep_5
main終了 18:35:42
"""
以下は並列処理を使ったコードです。
from concurrent.futures import ProcessPoolExecutor
import time
def sleep_10():
time.sleep(10)
print(f'sleep_10')
def sleep_5():
time.sleep(5)
print(f'sleep_5')
def main():
print(f"main開始 {time.strftime('%X')}")
with ProcessPoolExecutor(max_workers=2) as executor:
executor.submit(sleep_10)
executor.submit(sleep_5)
print(f"main終了 {time.strftime('%X')}")
if __name__ == "__main__":
main()
"""
# 標準出力の結果は以下
main開始 19:08:28
sleep_5
sleep_10
main終了 19:08:38
"""
並行処理:concurrency
並行処理とは?
- 1作業者が待ち時間を使って複数タスクを実行していくイメージ
- マルチスレッドによって実現(1プロセスでさらに複数のスレッドを立ち上げる)
- 待ち時間が長い、
IOバウンドな処理に有効 - pythonでは、
threadingやconcurrent.futureを使うことで実現
コードで学ぶ
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
def sleep_10():
time.sleep(10)
print(f'sleep_10')
def sleep_5():
time.sleep(5)
print(f'sleep_5')
def main():
print(f"main開始 {time.strftime('%X')}")
with ThreadPoolExecutor(max_workers=2) as executor:
executor.submit(sleep_10)
executor.submit(sleep_5)
print(f"main終了 {time.strftime('%X')}")
if __name__ == "__main__":
main()
# 標準出力の結果は以下
# main開始 19:08:28
# sleep_5
# sleep_10
# main終了 19:08:38
並列 VS 並行
Pythonの有名フレームワークのFastAPIのドキュメントでも述べられていますが、
並列処理と並行処理に優劣はありません。
並行は並列よりも優れていますか?¶
いや!それはこの話の教訓ではありません。
並行処理は並列処理とは異なります。多くの待機を伴う特定のシナリオに適しています。
そのため、一般に、Webアプリケーション開発では並列処理よりもはるかに優れています。しかし、すべてに対してより良いというわけではありません。
一般的なwebアプリケーションは、ファイルのダウンロードやアップロード・DBの読み書きなどIOバウンド処理にパフォーマンスのボトルネックがあることが多いため並行処理が有効な解決策となることが多いです。
一方で、CPUバウンド処理がある場合、並列処理が有効となります。CPUバウンド処理の例をFastAPIは以下のように記載しています。
CPUバウンド操作の一般的な例は、複雑な数学処理が必要なものです。
例えば:
オーディオ や 画像処理。
コンピュータビジョン: 中略
機械学習: 中略
ディープラーニング: 中略
非同期処理
非同期処理とは?
以下の文献から、Pythonにおける非同期処理=並行処理を簡単に扱えるようにしたもの と理解しました。
非同期コードのアイデアは、「並行処理」と呼ばれることもあります。 「並列処理」とは異なります。

用語整理
具体的なコードに入る前に、Pythonの非同期処理に関する用語をまとめます。
-
コルーチン:
- async def で定義される関数は コルーチン関数と呼ばれる
- コルーチン関数は平たく言うと、中断と再開ができる関数
- 普通の関数は return で一回終わったらもう戻ってこれないですが、コルーチンは await で 一時停止できる。停止した後も、また再開できる
-
イベントループ
- 「コルーチンを管理して、止まってるものを再開させる司令塔」
- Python の asyncio.run() を呼ぶと、裏でイベントループが起動して、「どのコルーチンが待ち状態か」や「どのコルーチンを次に再開させるか」を調整する
参考イメージ

コードで学ぶ
コルーチンにawaitをつけると実行されて処理が終わるまでここで待つ挙動になります。
ただ、awaitで処理が終わるまで待つため速度的には逐次処理と変わりません。
async def main ():
print(f"main開始 {time.strftime('%X')}")
# コールーチンにawaitをつけると実行されて処理が終わるまでここで待つ
print("10秒待つよ")
await asyncio.sleep(10)
print("5秒待つよ")
await asyncio.sleep(5)
print(f"main終了 {time.strftime('%X')}")
if __name__ == "__main__":
# asyncio.run : イベントループが作成されて、引数のコルーチンを実行
asyncio.run(main())
# main開始 20:14:22
# 10秒待つよ
# 5秒待つよ
# main終了 20:14:37
以下がasyncio.create_taskを使って並行処理にしてパフォーマンス改善しました。
async def main ():
print(f"main開始 {time.strftime('%X')}")
# コルーチンをwrapしたタスクが作成.taskは並行処理される
task1 = asyncio.create_task(asyncio.sleep(10))
task2 = asyncio.create_task(asyncio.sleep(5))
await task1
await task2
print(f"main終了 {time.strftime('%X')}")
if __name__ == "__main__":
# asyncio.run : イベントループが作成されて、引数のコルーチンを実行
asyncio.run(main())
# main開始 20:21:04
# main終了 20:21:14
参考文献
【非同期処理】Pythonの async / await 構文を使ってみよう!
【Pythonプログラミング】並列処理の基本を解説!マルチスレッド・マルチプロセスをconcurrent futuresで実装!
Discussion