非同期 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')