【发布时间】:2023-02-16 19:12:52
【问题描述】:
好的,所以我正在对 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)
我如何调用 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
这是 LOGS 在调用它们时从我的 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
请注意:创建了多个线程(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)
我如何调用 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
这是 LOGS 在调用它们时从我的 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
请注意:只有 1 个线程(140295303571264)
现在我无法弄清楚: 为什么在 SETUP-1 中,uvicorn 会产生超过 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
【问题讨论】:
-
FastAPI 在内部使用 40 个线程的线程池来处理使用非异步端点的请求。使用异步时,只使用一个线程(因为不等待的线程应该被 CPU 绑定的东西占用)。您通常可以告诉您的 ASGI 服务器运行多个 worker。
-
是的,“添加”
async到我的非异步 API 使其成为我想要的单线程。
标签: python python-asyncio fastapi uvicorn starlette