异步http调用占用的时间是它应该占用的时间的两倍

2024-04-18 15:47:33 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在学习异步和信号量,我已经使用fastapi做了一个端点来测试。fastapi端点只是一个简单的服务器端,它接受一个包含睡眠时间的请求,并在返回响应之前长时间睡眠。我通过uvicorn运行fastapi进行测试,但有5名工作人员。这只是为了测试,我知道在生产中我应该使用gunicorn和nginx,但为了学习,我只是使用uvicorn

uvicorn api_example:app --port 8008 --host cdsre.co.uk --workers 5

api_示例.py

from time import sleep, time
import json
from fastapi import FastAPI, Response, Request


app = FastAPI()


@app.post("/sleeper")
async def sleeper(request: Request):
    request_data = await request.json()
    sleep_time = request_data['sleep_time']
    start = time()
    sleep(sleep_time)
    end = time()
    return Response(content=json.dumps({"slept for": end - start}))

我的本地机器上的客户端代码试图利用异步和信号量来并行调用3个post请求。我有6个请求,睡眠计时器为5秒。因此,这里的期望是处理这6个请求大约需要10秒

异步\u示例.py

import aiohttp
import asyncio
import time


async def get_http_response(session, url):
    async with semaphore:
        print("firing request...")
        start = time.time()
        async with session.post(url, json={"sleep_time": 5}) as resp:
            response = await resp.text()
            end = time.time()
            print(f"Client time: {end - start}, server time: {response}")
            return response


async def main():
    async with aiohttp.ClientSession() as session:
        tasks = []
        for number in range(6):
            url = f'http://cdsre.co.uk:8008/sleeper'
            tasks.append(asyncio.ensure_future(get_http_response(session, url)))
        responses = await asyncio.gather(*tasks)
        for response in responses:
            pass
            # print(response)


semaphore = asyncio.Semaphore(3)
start_time = time.time()
asyncio.get_event_loop().run_until_complete(main())
print("--- %s seconds ---" % (time.time() - start_time))

然而,post请求通常需要两倍于它应该花费的时间。在这种情况下,睡眠计时器为5秒,一些请求需要10秒

firing request...
firing request...
firing request...
Client time: 5.137275695800781, server time: {"slept for": 5.005105018615723}
firing request...
Client time: 10.158655643463135, server time: {"slept for": 5.0042970180511475}
Client time: 10.158655643463135, server time: {"slept for": 5.001959800720215}
firing request...
firing request...
Client time: 5.055504560470581, server time: {"slept for": 5.005110025405884}
Client time: 5.056135654449463, server time: {"slept for": 5.005115509033203}
Client time: 5.107320070266724, server time: {"slept for": 5.005107402801514}
--- 15.271023750305176 seconds ---

有时它的速度是我的3倍,这是我睡眠时间的一个因素,这让我觉得有某种排队发生了,或者我错过了某种比赛条件,然而,我认为信号量模式的全部目的是避免这些竞争条件,这样我在任何时间限制为3个请求总是会少于服务器端(5个工作线程)上可用的工作线程,因此应该始终有一个可用的服务器端来处理它

我也不会开始计时,直到在信号灯内,所以我不会提前启动它,所以它应该只在发送请求时启动计时器。希望我只是错过了一些明显的东西。如果有人想试试的话,我已经把端点url放上去了。如果能帮我解决这个问题,我将不胜感激。本质上,我需要能够编写一个异步客户机,它可以并行发送请求,并在测量响应时间时保持一致

Exmaple,有些需要3倍的时间

firing request...
firing request...
firing request...
Client time: 15.127191305160522, server time: {"slept for": 5.001192808151245}
firing request...
Client time: 15.127155303955078, server time: {"slept for": 5.005094766616821}
Client time: 15.127155303955078, server time: {"slept for": 5.005074977874756}
firing request...
firing request...
Client time: 5.053789854049683, server time: {"slept for": 5.005076169967651}
Client time: 5.100871801376343, server time: {"slept for": 5.005076885223389}
Client time: 10.107984781265259, server time: {"slept for": 5.005110502243042}
--- 25.236175775527954 seconds ---

Tags: importclientasynciourlforasyncservertime
1条回答
网友
1楼 · 发布于 2024-04-18 15:47:33

继续评论:

You’re using time.sleep. This will block the thread. You want to use await asyncio.sleep(sleep_time). - @dirn (this is the answer)

However, having taken your comment and applied it in my code the results are a lot more consistent . So what is the difference between time.sleep in a worker and await asyncio.sleep.......I was thinking that each worker would get a separate request, so a sleep blocking that thread wouldn't affect the other - . @Chris Doyle

虽然我没有检查默认情况下uvunicorn是如何设置其工作线程的,但使用async进行编码的关键是在同一线程上完成所有工作,或者尽可能多地完成所有工作。 由于您的视图被定义为async,这是特别正确的-此编码范例不期望异步函数在等待任何调用时阻塞-线程将停止

如果您不需要调用sleep,而需要调用一些需要时间的代码,并且这些代码不是异步的,那么您可以通过在单独的线程中运行委托函数来实现异步调用。Python asyncio通过提供^{}调用使这变得很容易:它将自动创建一个带有线程池的基于线程的执行器,并在一个单独的线程中运行阻塞函数,同时释放异步事件循环的当前线程以进一步协调其他任务/工作者

否则,如前所述,如果您使用sleep进行测试,只需等待asyncio.sleep。您可以使用await loop.run_in_executor(None, time.sleep, 5)来检查它在阻塞函数中的行为

相关问题 更多 >