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を使用
このエントリーをはてなブックマークに追加
にほんブログ村 IT技術ブログへ

コメント

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です