非同期 I/O の紹介#

非同期 I/O は、IO(入出力)操作を備えたイベントループを提供する Python 標準ライブラリの一部です。Python でコンカレントプログラミングを行うために存在し、イベントループは以前のタスクが IO で待機している間、別タスクに切り替わります。このコンカレンシーにより、CPU の利用率が向上し、それによりスループットのパフォーマンスが向上します。

これについて理解する最も簡単な方法は、具体的なものを考慮すること、つまり実証的なシミュレーションです。以下では、シミュレートされた IO の遅延で URL を取得します。

import asyncio


async def simulated_fetch(url, delay):
    await asyncio.sleep(delay)
    print(f"Fetched {url} after {delay}")
    return f"<html>{url}"


def main():
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(
        simulated_fetch('http://google.com', 2),
        simulated_fetch('http://bbc.co.uk', 1),
    ))
    print(results)

以下の出力が表示されます。

>>> Fetched http://bbc.co.uk after 1
>>> Fetched http://google.com after 2
>>> ['<html>http://google.com', '<html>http://bbc.co.uk']

これは、google.com のフェッチを最初に呼び出しているにもかかわらず、bbc.co.uk が実際に最初に完了した、つまりコードが同時に実行されたことを示しています。さらに、このコードは同期のコードで予想される 3 秒ではなく、僅か 2 秒弱で実行されます。

Web サーバーとの関連性#

定義上、Web サーバーは、ネットワークからのリクエストを受信して応答するという点で IO を行います。つまり、フレームワーク内のコードそれ自体が IO を行っていなくても、非同期 I/O は非常に優れた適合性があります。しかし、実務では IO が存在します。たとえば、ファイルからテンプレートを読み込んだり、データベースまたは別のサーバーに接続するときなどです。

よくある落とし穴#

間違ったものを待機するのは非常に簡単です。たとえば、

await awaitable.attribute

は、想定されるawaitable オブジェクトではなく、属性を解決して待機しようとしています。これは、

await request.form.get('key')

としてよく見られ、コルーチンのラッパーに get 属性がないというエラーで失敗します。この問題を回避するには、単に角括弧を使用して最初に待機する必要があるものを示します。

(await awaitable).attribute
(await request.form).get('key')