Pythonでの非同期処理のサンプルコードをメモしておきます。
Python 3.9で動作確認をしました。
コード
サンプルコード
import asyncio
import time
# 非同期で実行する関数を定義
async def my_task(seconds):
print(f'Task: {seconds}秒待機開始')
# asyncio.sleepは非同期の待機を提供
await asyncio.sleep(seconds)
print(f'Task: {seconds}秒待機終了')
return f'結果: {seconds}秒待機した'
# 複数のタスクを非同期に実行する関数
async def run_tasks():
# 一度に実行するタスクを定義
tasks = [
my_task(1),
my_task(2),
my_task(3)
]
# asyncio.gatherを使用して複数のタスクを同時に実行し、全ての結果を待つ
results = await asyncio.gather(*tasks)
# 結果を表示
for result in results:
print(result)
# asyncio.runを使用して非同期関数を実行
if __name__ == '__main__':
start_time = time.time()
asyncio.run(run_tasks())
end_time = time.time()
print(f"実行時間: {end_time - start_time}秒")
同期関数を定義し、それを非同期に実行しています。
*tasks
の*
は、Pythonのアンパック演算子です。特に、このコンテキストでは、リスト内の要素を個別の引数としてasyncio.gather()
関数に渡すために使用されています。
実行結果は以下のとおりです。
Task: 1秒待機開始
Task: 2秒待機開始
Task: 3秒待機開始
Task: 1秒待機終了
Task: 2秒待機終了
Task: 3秒待機終了
結果: 1秒待機した
結果: 2秒待機した
結果: 3秒待機した
実行時間: 3.0242197513580322秒
3つのタスクがほぼ同時に実行されたことにより、処理全体の実行時間が、合計待機時間(6秒)ではなく、最長のタスクの待機時間(3秒)に近くなっています。
出力の表示順序からも、他タスクの完了を待っていないことが分かります。asyncio.run(run_tasks())により、非同期的に処理が実現できています。
同時実行数の制限
同時実行数の制限には、いくつかの方法がありますが、よく使われるのはasyncio.Semaphore
を利用する方法です。
Semaphore
は、同時に実行できるタスクの数を制限するための同期プリミティブです。この方法では、Semaphore
の最大数を設定して、その数だけ同時に実行されるタスクを制限します。
以下は、最大同時実行数を3に制限するコードです。
import asyncio
async def task(name, semaphore):
async with semaphore:
# 処理内容
async def main():
semaphore = asyncio.Semaphore(3) # 同時に3つのタスクのみを許可
tasks = []
for i in range(10): # 10個のタスクを生成
task_name = f'task-{i}'
tasks.append(task(task_name, semaphore))
await asyncio.gather(*tasks) # 全てのタスクを同時に実行
asyncio.run(main())
asyncioについて
asyncioは、Pythonで非同期プログラミングをサポートするためのライブラリです。
非同期プログラミングは、I/O操作(ファイル読み書き、ネットワークリクエストなど)の待ち時間を効率的に利用し、プログラムの実行をブロックせず並行処理を行う方法です。
この方法により、特にI/Oがボトルネックとなるアプリケーションのパフォーマンスを向上させることができます。
async
async
キーワードは、関数を非同期関数(coroutine)として定義するために使用されます。非同期関数は、呼び出されるとすぐに実行されるのではなく、await式を介して呼び出されたときに実行を開始します。
非同期関数は、実行を一時停止し、制御をイベントループに戻すことができるポイントを提供します。これにより、他のタスクが実行される機会が生まれます。
async def fetch_data():
# 何らかの非同期I/O操作を行う
pass
await
await
キーワードは、非同期実行を一時停止し、非同期関数の結果が利用可能になるまで待機するために使用されます。
await
は、async
で定義された関数内でのみ使用できます。await
の後には、非同期関数の呼び出しや、asyncio
ライブラリが提供する他の非同期操作が来ます。
この待機は非ブロッキングであり、イベントループが他のタスクを実行できるようにします。
async def main():
# fetch_dataが完了するまで非同期に待機
await fetch_data()
asyncio.run()
asyncio.run()
を使うと、非同期関数(coroutine)を簡単に実行し、その実行を管理するイベントループを自動的に作成し、実行し、閉じることができます。
具体的には、asyncio.run(main())
のように使用し、main()
が非同期関数である場合、asyncio.run()
は以下のステップを自動で行います:
- イベントループの作成
新しいイベントループを作成し、現在のコンテキストでそれを設定します。 - 非同期関数の実行
引数として渡された非同期関数(この例ではmain()
)をイベントループで実行します。この関数が終了するまで、イベントループは実行を続けます。 - イベントループのクローズ
非同期関数の実行が完了したら、イベントループを閉じます。これにより、関連するリソースがクリーンアップされます。
asyncio.run()
は非同期プログラムのエントリーポイントとして設計されており、単一の非同期関数を引数に取ります。
この関数は、その非同期関数が完了するまで実行を続け、関数からの戻り値を返します。また、プログラムが終了する前にイベントループを適切に閉じることを保証します。