3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FastAPIのasync def表記を深掘りしてみた

Last updated at Posted at 2023-02-22

async defとは、fastapiにおける頻出表記ですね。いつもちゃんと理解ができなくて、あやふややってきました。今年2月時点で、fastapi公式ドキュメントの並行処理とasync/awaitがようやく日本語化され、それを読んでから記事を作成しました。(訳者さんに感謝〜)

結論

APIサーバーの動き

fastapiは、クライアントサーバ(client-server, cs)システムの一つです。サーバー上では、fastapiアプリはASGIサーバー(uvicornが一般的)から起動されます。uvicorn起動オプションの一つ--workersでスレッド数が指定されます。デフォルトでは環境変数WEB_CONCURRENCYを参照し、ない場合は1とします。
多重化(multiplexing)技術を使って、複数のTCP接続を一つのスレッドで管理します。

async defとdefの違い

  • Path operation関数asyncで宣言すると、fastapiのメインスレッドで直接実行されます。awaitされるので、メインスレッドをブロックします。時間消費が多い場合、サーバーが新しいリクエストへ返答が遅くなる可能性があります。
  • 一方で、defで宣言すると、メインスレッドじゃなくて外部スレッドプールで実行されます。メインスレッドをブロックしないが、cpuに負荷をかけます。

結局、どっちを使うのか

答えは公式ドキュメントにあります。

path operation 関数を async def の代わりに通常の def で宣言すると、(サーバーをブロックするので) 直接呼び出す代わりに外部スレッドプール (awaitされる) で実行されます。

上記の方法と違った方法の別の非同期フレームワークから来ており、小さなパフォーマンス向上 (約100ナノ秒) のために通常の def を使用して些細な演算のみ行う path operation 関数 を定義するのに慣れている場合は、FastAPIではまったく逆の効果になることに注意してください。このような場合、path operation 関数 がブロッキングI/Oを実行しないのであれば、async def の使用をお勧めします。
参照先:https://fastapi.tiangolo.com/ja/async/#path-operation

コードで検証

"async def"と"def"違いを検証するために、demoを用意しました。

import threading
from fastapi import FastAPI
from time import sleep
from hashlib import md5
from random import randint

app = FastAPI()


def work(n):
    sleep(n)
    b = randint(602, 2021).to_bytes(4, 'big')
    return md5(b).hexdigest()


async def awork(n):
    sleep(n)
    b = randint(602, 2021).to_bytes(4, 'big')
    return md5(b).hexdigest()


@app.get('/awork')
async def awk(n: int = 7):
    res = await awork(n)
    print(123)
    return res


@app.get('/work')
def wk(n: int = 7):
    res = work(n)
    return res


@app.get('/aping')
async def aping():
    return 'APONG!'


@app.get('/ping')
def ping():
    return 'PONG!'


@app.get('/tid')
def tid():
    return threading.get_ident()

github: https://github.com/tdzz1102/fastapi-async-what

スクリーンショット 2023-02-22 16.32.04.png

localhost:8000でサーバーを立ち上げて、api docsはこうなっている。

work(), awork()の中ではブロッキング処理があります。唯一の違いは、awork()はasyncで宣言されます。それぞれにapiも作成しました。ping(), aping()の役割は、サーバーが今ブロックされるかの確認。tid()threading.get_ident()、つまりスレッドidを取得する関数です。

操作 結果 原因
ブラウザの違うページから/tidを実行 tidは同じ fastapiはシングルスレッド
/workしてからすぐに/pingを実行 /pingはすぐに結果が返される 両方とも外部スレッドプール実行されるので、メインスレッドをブロックしない
/workしてからすぐに/apingを実行 /apingはすぐに結果が返される /workは外部スレッドプール実行されるので、メインスレッドをブロックしない
/aworkしてからすぐに/apingを実行 /apingは約7秒後に結果が返される メインスレッドは/aworkを待つ(await)ので、7秒間ブロックされる
/aworkしてからすぐに/pingを実行 /pingは約7秒後に結果が返される メインスレッドは/aworkを待つ(await)ので、7秒間ブロックされる。/pingはスレッドプールが代わりに実行するが、それにリクエストの「受け取り-スレッド作成-結果を待つ」という流れも、メインスレッドの役割だから
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?