Pythonasyncioモジュールを使用すると、非同期プログラミングをシンプルかつ効率的に行うことができます。従来の同期プログラミングでは、I/O操作(ファイル操作やネットワーク通信など)の待ち時間中にプログラムが停止してしまうことがありましたが、非同期プログラミングを使うことで、複数の処理を並行して実行し、システムの効率を向上させることができます。この記事では、asyncioの基本的な使い方を紹介し、Pythonでの並行処理の基礎を学びます。

非同期プログラミングとは?

非同期プログラミングは、ある処理の完了を待つ間に他の処理を進めることができるプログラミング手法です。通常の同期プログラムでは、ひとつの処理が完了するまで他の処理はブロックされますが、非同期プログラムでは、待ち時間を効率的に使って複数のタスクを同時に進行できます。 例えば、ウェブからデータを取得する際、従来の方法ではレスポンスを待っている間、他の作業が止まってしまいますが、非同期プログラミングを使うと、他の処理を並行して進めることができます。

asyncioとは?

asyncioは、Pythonの標準ライブラリであり、非同期I/Oや並行処理を簡単に扱えるように設計されています。asyncioはイベントループを使って、複数のタスクを並行して処理し、効率的なプログラムの実行をサポートします。主に、ネットワークやファイルのI/O処理の最適化に利用されます。

主な機能

  • イベントループ:複数のタスクをスケジュールして並行処理を行う中心的な仕組み。
  • コルーチン:非同期処理を行うための特別な関数。
  • タスク:コルーチンを並行して実行するための管理単位。

async/await構文

Pythonで非同期プログラミングを行うためには、asyncawaitという構文を使用します。

  • async:非同期関数(コルーチン)を定義するために使用します。
  • await:コルーチンや非同期タスクの完了を待つ際に使用します。

基本的な使い方

import asyncio
# 非同期関数の定義
async def say_hello():
    print("Hello")
    await asyncio.sleep(1)  # 1秒待つ(非同期的に)
    print("World")
# イベントループを実行
asyncio.run(say_hello())

コードの解説

  • async def say_hello():非同期関数(コルーチン)を定義します。
  • await asyncio.sleep(1):1秒間待機しますが、この待ち時間中に他のタスクを実行することができます。
  • asyncio.run():指定されたコルーチンを実行するためのエントリーポイントです。 このプログラムを実行すると、“Hello”と表示された後1秒間待ち、次に”World”が表示されます。

asyncioを使った非同期タスクの作成

asyncio.create_task()を使うと、複数のタスクを並行して実行することができます。これにより、時間のかかる処理を効率的に並行処理できます。

複数タスクの実行例

import asyncio
async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")
async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")
async def main():
    # 2つのタスクを並行して実行
    task_1 = asyncio.create_task(task1())
    task_2 = asyncio.create_task(task2())
    # すべてのタスクが完了するのを待つ
    await task_1
    await task_2
# イベントループを実行
asyncio.run(main())

実行結果

Task 1 started
Task 2 started
Task 2 finished
Task 1 finished

コードの解説

  • asyncio.create_task():タスクを作成し、イベントループに登録して並行して実行します。
  • await:タスクの完了を待ちます。これにより、各タスクが終了するまでの間に他のタスクが実行されます。 この例では、task1が2秒、task2が1秒の遅延がある処理ですが、task2が1秒後に終了し、その後task1が2秒後に終了します。これにより、2つの処理が並行して行われていることがわかります。

asyncio.gather()によるタスクのまとめ

asyncio.gather()を使うと、複数のコルーチンを一度に実行し、その結果をまとめて取得することができます。これは、複数の非同期処理を同時に行いたい場合に非常に便利です。

タスクのまとめ実行

import asyncio
async def download_file(file_id):
    print(f"Downloading file {file_id}...")
    await asyncio.sleep(file_id)  # ダウンロードにかかる時間をシミュレート
    print(f"File {file_id} downloaded.")
    return file_id
async def main():
    # 複数のファイルを並行してダウンロード
    results = await asyncio.gather(
        download_file(1),
        download_file(2),
        download_file(3)
    )
    print("All downloads complete:", results)
# イベントループを実行
asyncio.run(main())

実行結果

Downloading file 1...
Downloading file 2...
Downloading file 3...
File 1 downloaded.
File 2 downloaded.
File 3 downloaded.
All downloads complete: [1, 2, 3]

コードの解説

  • asyncio.gather():複数の非同期タスクをまとめて実行し、それらの完了を待ちます。この場合、download_file()が3つ同時に実行されます。
  • 結果はリストとして返され、全てのタスクが完了したタイミングで次の処理が実行されます。

非同期I/O操作の活用

非同期プログラミングは、特にI/O待ち(ネットワーク 通信やファイル操作)が多い場合に効果的です。以下は、非同期的にWebページを取得する例です。

非同期Webリクエストの例

import asyncio
import aiohttp
async def fetch_page(session, url):
    async with session.get(url) as response:
        print(f"Fetching {url}")
        return await response.text()
async def main():
    urls = [
        'https://example.com',
        'https://example.org',
        'https://example.net'
    ]
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_page(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
    
    print("All pages fetched.")
# イベントループを実行
asyncio.run(main())

コードの解説

  • aiohttp.ClientSession():非同期HTTPリクエストを行うためのセッションを作成します。
  • async with:セッションとリクエストの非同期コンテキストを管理します。
  • 複数のURLに対して並行してリクエストを送り、レスポンスを取得しています。

asyncioの制限と考慮点

asyncioは効率的な並行処理を可能にしますが、CPUバウンドな処理には向いていません。CPU負荷が高い処理は、スレッドやプロセスを利用する別の並行処理手法を検討する必要があります。 また、asyncioはPython 3.7以降で大きく改善されており、asyncio.run()などの新しい機能が追加されていますので、最新版のPythonで利用することをおすすめします。

まとめ

asyncioを使うことで、Pythonで非同期プログラミングを簡単に実現できます。非同期処理を導入することで、複数のI/O操作を効率的に並行処理でき、プログラムのパフォーマンスを大幅に向上させることが可能です。今回紹介したasync/awaitの構文やasyncio.gather()を活用し、非同期タスクを効率的に管理しながら、より複雑な並行処理にも挑戦してみてください。