Python非同期処理完全ガイド:asyncio・async/awaitの基礎と実践

Pythonの非同期処理をasyncioとasync/awaitで学ぶ。並行処理の基本から実装例、注意点まで徹底解説。

Python非同期asyncioasyncawait並行処理2026/5/25

はじめに

Pythonの非同期処理は、I/O待ち時間を有効活用してプログラムの処理効率を向上させる手法です。asyncioモジュールとasync/await構文を用いることで、シングルスレッドでも複数のタスクを並行して実行できます。本記事では、非同期処理の基本概念から具体的な実装方法、注意点までを解説します。

非同期処理の基本概念

同期処理 vs 非同期処理

通常の同期処理では、一つの処理が完了するまで次の処理は開始されません。一方、非同期処理では、待ち時間が発生する処理(例:ネットワークリクエスト、ファイル読み書き)の間に別の処理を実行できます。

並行性と並列性の違い

  • 並行性 (Concurrency): 複数のタスクを切り替えながら実行。シングルコアでも可能。
  • 並列性 (Parallelism): 複数のタスクを同時に実行。マルチコアが必要。
  • 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で捕捉します。タスク内の例外はgatherreturn_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

    まとめ

    asyncioasync/awaitを使うことで、I/Oバウンドな処理を効率的に並行実行できます。非同期処理の導入により、Webスクレイピング、APIコール、ファイル操作などでパフォーマンス向上が期待できます。ただし、CPUバウンドな処理には向かないため、適切な使い分けが重要です。

    最初はシンプルなコードから練習し、徐々に複雑な非同期アプリケーションに挑戦してみてください。