Uvicorn / Fastapi 如何处理 1 个工作线程和同步端点的并发?

poi*_*rez 2 python python-asyncio fastapi uvicorn

了解 Uvicorn 异步行为

我试图了解 Uvicorn 的行为。我创建了一个示例 fastapi 应用程序,主要休眠 5 秒。

import time
from datetime import datetime


from fastapi import FastAPI


app = FastAPI()

counter = 0

@app.get("/")
def root():
    global counter
    counter = counter + 1
    my_id = counter
    print(f'I ({my_id}) am feeling sleepy')
    time.sleep(5)
    print(f'I ({my_id}) am done sleeping')
    return {}
Run Code Online (Sandbox Code Playgroud)

我使用 Apache Bench 的以下命令调用我的应用程序:

ab -n 5 -c 5 http://127.0.0.1:8000/
Run Code Online (Sandbox Code Playgroud)

输出:

I (1) am feeling sleepy  -- 0s
I (1) am done sleeping   -- 5s
I (2) am feeling sleepy  -- 5s
I (3) am feeling sleepy  -- 5s
I (4) am feeling sleepy  -- 5s
I (5) am feeling sleepy  -- 5s
I (2) am done sleeping   -- 10s
I (4) am done sleeping   -- 10s
I (3) am done sleeping   -- 10s
I (5) am done sleeping   -- 10s
Run Code Online (Sandbox Code Playgroud)

为什么请求会同时运行?我运行该应用程序为:

uvicorn main:app --workers 1
Run Code Online (Sandbox Code Playgroud)

请注意,我没有使用 async 关键字,所以对我来说一切都应该完全同步。

来自 FastAPI 文档:

当您使用普通 def 而不是异步 def 声明路径操作函数时,它会在等待的外部线程池中运行,而不是直接调用(因为它会阻塞服务器)。

这个线程池在哪里?当我使用睡眠时,我认为唯一可用的工作人员将被完全阻止。

poi*_*rez 5

FastAPI 使用 Starlette,而 Starlette 在幕后使用 AnyIO。默认提供40个线程的线程池来处理同步请求。这个线程池是并发同步请求的多次执行的魔力背后的原因。

可以这样配置:

from anyio.lowlevel import RunVar
from anyio import CapacityLimiter

app = FastAPI()

@app.on_event("startup")
def startup():
    print("start")
    RunVar("_default_thread_limiter").set(CapacityLimiter(2))
Run Code Online (Sandbox Code Playgroud)

来源:https ://github.com/tiangolo/fastapi/issues/4221#issuecomment-982260467

感谢@MatsLindh。