为什么 UVICORN/Starlette/FastAPI 在不使用“ASYNC”时会产生更多线程,而在使用“ASYNC”时却不会?

rag*_*ria 5 python python-asyncio starlette fastapi uvicorn

好的,所以我正在对 进行比较研究using ASYNC vs without using ASYNC in FastAPI。然而,我得到了一些意想不到的结果,并且不明白为什么。

这是设置 1:

不使用异步的 API

import uvicorn
from fastapi import FastAPI
import PIL.Image as Image
import requests
from loguru import logger
import sys

log_format = "{level} {process}-{thread} {time} {name}:{line} - {message}"
logger.remove()
logger.add(sys.stderr, format=log_format, backtrace=True, diagnose=True)
logger.add("logs/" + "t_{time}.log", format=log_format, colorize=True, backtrace=True, diagnose=True)

Image.MAX_IMAGE_PIXELS = None

def get_the_image_from_net():
    a = requests.get("https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73751/world.topo.bathy.200407.3x21600x21600.A1.jpg")
    return True


app = FastAPI()

@app.get("/expectoPatronum")
def get_image_of_the_new_world():
    """
    Gets Image of the World
    """
    logger.info("Received request for getting Image of the world")
    image_bytes: bytes = get_the_image_from_net()
    logger.info("Image has been downloaded and processed as bytes array")
    return True

uvicorn.run(app, host="0.0.0.0", port=10009)
Run Code Online (Sandbox Code Playgroud)

我如何调用 ABOVE API(通过使用多处理池)

import time
from multiprocessing import Pool, Manager
import requests


def test_function(index_iterator: int):
    start_time = time.time()
    response = requests.get("http://localhost:10009/expectoPatronum")
    print(f"response.content: {str(response.content)}")
    if response.status_code != 200:
        print("----------------------NOT 200")
        print(f"response.content: {str(response.content)}")
    end_time = time.time()
    elapsed_time = end_time - start_time


pool = Pool(5)
pool.map(test_function, [i for i in range(1,6)])
pool.close
Run Code Online (Sandbox Code Playgroud)

这是调用 API 时从我的 API 端读取的日志:

INFO 9408-140671786272512 2022-07-13T01:32:37.498465+0530 __main__:27 - Received request for getting Image of the world
INFO 9408-140671777879808 2022-07-13T01:32:37.501623+0530 __main__:27 - Received request for getting Image of the world
INFO 9408-140671769487104 2022-07-13T01:32:37.504744+0530 __main__:27 - Received request for getting Image of the world
INFO 9408-140671760897792 2022-07-13T01:32:37.504929+0530 __main__:27 - Received request for getting Image of the world
INFO 9408-140671752242944 2022-07-13T01:32:37.505638+0530 __main__:27 - Received request for getting Image of the world
INFO 9408-140671786272512 2022-07-13T01:33:04.845982+0530 __main__:29 - Image has been downloaded and processed as bytes array
INFO 9408-140671777879808 2022-07-13T01:33:16.167435+0530 __main__:29 - Image has been downloaded and processed as bytes array
INFO 9408-140671769487104 2022-07-13T01:33:17.284000+0530 __main__:29 - Image has been downloaded and processed as bytes array
INFO 9408-140671752242944 2022-07-13T01:33:37.771086+0530 __main__:29 - Image has been downloaded and processed as bytes array
INFO 9408-140671760897792 2022-07-13T01:33:38.016435+0530 __main__:29 - Image has been downloaded and processed as bytes array
Run Code Online (Sandbox Code Playgroud)

请注意:创建了多个线程(140671786272512、140671777879808 ..等等)

这是设置2:

使用异步的 API

import uvicorn
from fastapi import FastAPI, Response, APIRouter
import httpx
from loguru import logger
import sys

log_format = "{level} {process}-{thread} {time} {name}:{line} - {message}"
logger.remove()
logger.add(sys.stderr, format=log_format, backtrace=True, diagnose=True)
logger.add("logs/" + "t_{time}.log", format=log_format, colorize=True, backtrace=True, diagnose=True)

Image.MAX_IMAGE_PIXELS = None

async def get_the_image_from_net():
    async with httpx.AsyncClient() as client:
        a = await client.get('https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73751/world.topo.bathy.200407.3x21600x21600.A1.jpg')

    return True


app = FastAPI()

@app.get("/expectoPatronum")
async def get_image_of_the_new_world():
    """
    Gets Image of the World
    """
    logger.info("Received request for getting Image of the world")
    image_bytes = await get_the_image_from_net()
    logger.info("Image has been downloaded and processed as bytes array")
    return True

uvicorn.run(app, host="0.0.0.0", port=10008)
Run Code Online (Sandbox Code Playgroud)

我如何调用 ABOVE API(通过使用多处理池(与上面的调用方法相同,但端口号不同)

import time
from multiprocessing import Pool, Manager
import requests


def test_function(index_iterator: int):
    start_time = time.time()
    response = requests.get("http://localhost:10008/expectoPatronum")
    print(f"response.content: {str(response.content)}")
    if response.status_code != 200:
        print("----------------------NOT 200")
        print(f"response.content: {str(response.content)}")
    end_time = time.time()
    elapsed_time = end_time - start_time


pool = Pool(5)
pool.map(test_function, [i for i in range(1,6)])
pool.close
Run Code Online (Sandbox Code Playgroud)

这是调用 API 时从我的 API 端读取的日志:

INFO 9442-140295303571264 2022-07-13T01:36:26.762525+0530 __main__:43 - Received request for getting Image of the world
INFO 9442-140295303571264 2022-07-13T01:36:26.776561+0530 __main__:43 - Received request for getting Image of the world
INFO 9442-140295303571264 2022-07-13T01:36:26.783669+0530 __main__:43 - Received request for getting Image of the world
INFO 9442-140295303571264 2022-07-13T01:36:26.790367+0530 __main__:43 - Received request for getting Image of the world
INFO 9442-140295303571264 2022-07-13T01:36:26.796934+0530 __main__:43 - Received request for getting Image of the world
INFO 9442-140295303571264 2022-07-13T01:37:38.086156+0530 __main__:45 - Image has been downloaded and processed as bytes array
INFO 9442-140295303571264 2022-07-13T01:37:43.709798+0530 __main__:45 - Image has been downloaded and processed as bytes array
INFO 9442-140295303571264 2022-07-13T01:37:43.827959+0530 __main__:45 - Image has been downloaded and processed as bytes array
INFO 9442-140295303571264 2022-07-13T01:37:47.218717+0530 __main__:45 - Image has been downloaded and processed as bytes array
INFO 9442-140295303571264 2022-07-13T01:37:51.712889+0530 __main__:45 - Image has been downloaded and processed as bytes array
Run Code Online (Sandbox Code Playgroud)

请注意:只有 1 个线程 (140295303571264)

现在我无法弄清楚:为什么在 SETUP-1 中,uvicorn 会产生超过 1 个线程?我认为它是一个单线程服务器,如何限制它在进程内仅生成 1 个线程?

这两种设置的时间结果确实令人沮丧。对于不使用异步的 SETUP-1,时间为:27.36284899711609,38.68544268608093,39.79848337173462,60.28416633605957,60.529775857925415

对于使用异步的 SETUP-2,时间为:71.32960891723633,76.95274710655212,77.07157778739929,80.4650149345398,84.95978450775146

小智 4

当您使用普通def而不是声明路径操作函数时,它会在等待的async def外部运行,而不是直接调用(因为它会阻塞服务器)。threadpool您可以在 FastAPI 文档中阅读更多内容