Pythonのasyncioセマフォで処理を順番待ちさせる方法
非同期処理で「同時に動くタスクが多すぎて外部APIやDBに負荷がかかる」「特定の処理だけは1つずつ順番に実行したい」と困ることがあります。こうした場面では、セマフォ(Semaphore)で同時実行数を制限すると解決できます。この記事では、asyncio.Semaphoreで処理を順番待ちさせる方法を、2つの使用例で説明します。
セマフォとは
セマフォは、同時に実行できるタスクの数を制限する仕組みです。特定のリソースへのアクセスを制御したいときに使います。valueに指定した数まで同時に実行でき、上限を超えたタスクは空きが出るまで待たされます。
value=1: 1つずつ順番に実行value=2: 最大2つまで同時に実行
使うセマフォの選び方
Pythonのセマフォには、用途別に2種類あります。
asyncio.Semaphore: 非同期処理(asyncio)用threading.Semaphore: スレッド用
どちらも標準ライブラリなので、追加インストールは不要です。本記事ではasyncio.Semaphoreを扱います。
非同期処理とは、ある処理の完了を待っている間に別の処理を進める仕組みです。Pythonでは標準ライブラリのasyncioを使うと書けます。非同期I/Oやネットワーク通信を効率よく扱いたいときに役立ちます。
使用例1:同時実行が複数可能な場合
value=2にして、3秒かかる処理と1秒かかる処理の2つを動かします。
import asyncio
# value: 同時実行可能な数
semaphore = asyncio.Semaphore(value=2)
async def process_item(min_sec):
await asyncio.sleep(min_sec)
#;
async def process_items(min_sec: int = 0):
async with semaphore:
result = await process_item(min_sec)
print(f"param: {min_sec}")
#;
async def main():
task1 = asyncio.create_task(process_items(3))
task2 = asyncio.create_task(process_items(1))
await task1
await task2
#;
if __name__ == "__main__":
asyncio.run(main())
実行結果
param: 1
param: 3
上限が2なので、2つのタスクは待たされずに同時に動きます。そのため、終わった順(1秒の処理、3秒の処理)に表示され、全体の所要時間は約3秒です。この例は順番待ちにはなりません。
使用例2:同時実行が単一の場合
同じコードでvalue=1に変えると、1つずつ順番に実行されます。先に始まった3秒の処理が終わってから1秒の処理が動くので、出力はparam: 3、param: 1の順になり、全体で約4秒かかります。
import asyncio
# value: 同時実行可能な数
semaphore = asyncio.Semaphore(value=1)
async def process_item(min_sec):
await asyncio.sleep(min_sec)
#;
async def process_items(min_sec: int = 0):
async with semaphore:
result = await process_item(min_sec)
print(f"param: {min_sec}")
#;
async def main():
task1 = asyncio.create_task(process_items(3))
task2 = asyncio.create_task(process_items(1))
await task1
await task2
#;
if __name__ == "__main__":
asyncio.run(main())
実行結果
param: 3
param: 1
value=1にすると、同時に実行できるのは1つだけです。async with semaphore:で囲んだ範囲に入れるのは常に1つのリクエストで、残りは前の処理が終わるまで待ちます。
まとめ
- 同時実行数の制限には
asyncio.Semaphore(value=N)を使用 - 制限したい処理は
async with semaphore:で囲む形 - 1つずつ順番に処理したい場合は
value=1を指定 - スレッドで同じことをしたい場合は
threading.Semaphoreを使用
