当サイトは、アフィリエイト広告を利用しています
Pythonで非同期処理async/awaitを書いていて
何か、jsのasync/awaitとは違うような気がしたので少し調べてみた。
当記事では
を構造的に整理する
JavaScriptの非同期処理は、イベントループを中心とした実行モデルで成り立っていて
本質は Promise とイベントループにある。
javascriptにおいては
は分けて考えるておくことで、Pythonと比較する時に理解しやすい
async function main() {console.log("start");fetch("/api/user");console.log("end");}
この場合、fetch()はPromiseを返す関数であるため、処理フローは
のようにfetch()の処理を待たずに実行されていく。
JavaScriptにおける非同期処理は
awaitを付けなくてもPromise自体は生成され、処理は進行する。
async function main() {console.log("start");await fetch("/api/user");console.log("end");}
とした場合の処理フローは
となる。
JavaScriptにおけるawaitは
であり、
非同期処理を開始するための構文ではなく、
すでに開始されている Promise の完了を待つための構文である。
Pythonの非同期処理もjavascriptと同様に、イベントループを中心とした実行モデルで成り立っているが Pythonにおいては
はセットで考える必要がある。
※Pythonにおいてawaitの役割が異なる。
Pythonでは async def を用いて非同期関数を定義する。
※これをコルーチンと呼ぶ
async def fetch_user():...coro = fetch_user()
つまりPythonでは
という特徴がある。
実際に処理を実行するためには、
await を使ってイベントループに登録する必要がある。
async def main():await fetch_user()
await に到達すると、コルーチンはイベントループに登録され実行される。概念的な流れは次のようになる。
Pythonの非同期処理は
という構造で実現されている。
Pythonの標準実装では、この仕組みは asyncio モジュールによって提供されている。
Pythonでは await されないコルーチンが存在すると警告が発生する。
fetch_user()
このようにコルーチンを生成しただけで放置すると
RuntimeWarning: coroutine was never awaited
という警告が出る。 これはコルーチンが実行されないまま破棄される可能性があるためである。
つまりPythonの await は
という 2つの役割 を持つ。
※コルーチンは await されるか Task としてスケジュールされない限り実行されない。
ここまで見てきたように、 JavaScriptとPythonの非同期処理にはいくつかの共通点がある。
まず両者とも イベントループ型の並行処理モデル を採用している。
このモデルでは
ことで並行処理を実現している。
つまり両言語とも
async / awaitという仕組みによって、 I/O待ち時間を効率的に利用する設計になっている。
また重要な点として
CPUを同時実行しているわけではなく、
イベントループによって処理を切り替えている。
ここまでの内容を踏まえると、 JavaScriptとPythonの非同期処理の最大の違いは
にある。
JavaScriptでは
で処理が開始される
fetch("/api/user");
この例では
await はその完了を待つかどうかを決める構文であるため実行とは関係がない。一方、Pythonではコルーチンは await されるまで実行されない
fetch_user()
このコードではコルーチンオブジェクトが生成されるだけで、 処理はまだ開始されない。
実行するには
await fetch_user()
違いを整理すると、次のようになる。
つまり
となる。
この設計の違いにより、
JavaScriptでは await を付けなくても非同期処理は進行するが、
Pythonでは await を付けないとコルーチンが実行されず警告が発生する。
なお、FastAPIなどのPythonの非同期Webフレームワークでは、
エンドポイント関数自体は内部で await されて実行されている。
詳細は下記記事
そのためエンドポイント関数内でさらに非同期処理を行う場合は、
明示的に await を付ける必要がある。