Python非同期処理完全ガイド:asyncio・async/awaitの基礎と実践
Pythonの非同期処理をasyncioとasync/awaitで学ぶ。並行処理の基本から実装例、注意点まで徹底解説。
はじめに
Pythonの非同期処理は、I/O待ち時間を有効活用してプログラムの処理効率を向上させる手法です。asyncioモジュールとasync/await構文を用いることで、シングルスレッドでも複数のタスクを並行して実行できます。本記事では、非同期処理の基本概念から具体的な実装方法、注意点までを解説します。
非同期処理の基本概念
同期処理 vs 非同期処理
通常の同期処理では、一つの処理が完了するまで次の処理は開始されません。一方、非同期処理では、待ち時間が発生する処理(例:ネットワークリクエスト、ファイル読み書き)の間に別の処理を実行できます。
並行性と並列性の違い
asyncioは並行性を提供し、I/Oバウンドな処理に適しています。CPUバウンドな処理にはmultiprocessingやスレッドが適しています。
asyncioの基礎
イベントループ
イベントループは非同期タスクを管理し、実行可能なタスクを順に実行します。asyncio.run()でイベントループを開始します。
コルーチン
async defで定義する関数をコルーチンと呼びます。コルーチンはawaitで別のコルーチンを呼び出せます。
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
asyncio.run(say_hello())
await式
awaitはコルーチンの実行を一時停止し、別のタスクに制御を移します。awaitableオブジェクト(コルーチン、Task、Future)に対して使用します。
複数タスクの同時実行
create_taskによるタスク生成
asyncio.create_task()でコルーチンをTaskとしてスケジュールし、並行実行します。
async def main():
task1 = asyncio.create_task(say_hello())
task2 = asyncio.create_task(say_hello())
await task1
await task2
gatherによる一括待機
複数のタスクを同時に実行し、すべて完了するまで待つにはasyncio.gather()を使います。
async def main():
await asyncio.gather(
say_hello(),
say_hello(),
say_hello()
)
実践的な非同期コード例
非同期HTTPリクエスト
aiohttpライブラリを使用した非同期HTTPリクエストの例です。
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://example.com')
print(html[:100])
asyncio.run(main())
非同期ファイル読み書き
aiofilesライブラリを使うとファイル操作も非同期化できます。
import aiofiles
import asyncio
async def read_file():
async with aiofiles.open('data.txt', mode='r') as f:
contents = await f.read()
print(contents)
asyncio.run(read_file())
注意点とベストプラクティス
ブロッキング処理を避ける
time.sleep()のようなブロッキング処理はイベントループを止めるため、asyncio.sleep()を使用します。また、requestsライブラリはブロッキングするため、aiohttpなど非対応ライブラリを使いましょう。
デバッグとエラーハンドリング
例外はtry/exceptで捕捉します。タスク内の例外はgatherのreturn_exceptions=Trueで取得可能です。
async def may_fail():
raise ValueError("エラー")
async def main():
results = await asyncio.gather(
may_fail(),
return_exceptions=True
)
for r in results:
if isinstance(r, Exception):
print(f"エラー: {r}")
スレッドセーフではない
asyncioはシングルスレッドで動作するため、共有リソースへのアクセスには注意が必要です。ロック機構としてasyncio.Lockが用意されています。
lock = asyncio.Lock()
async def critical_section():
async with lock:
# 排他制御が必要な処理
pass
まとめ
asyncioとasync/awaitを使うことで、I/Oバウンドな処理を効率的に並行実行できます。非同期処理の導入により、Webスクレイピング、APIコール、ファイル操作などでパフォーマンス向上が期待できます。ただし、CPUバウンドな処理には向かないため、適切な使い分けが重要です。
最初はシンプルなコードから練習し、徐々に複雑な非同期アプリケーションに挑戦してみてください。