Asyncio 使 HTTP 请求变慢?

Asyncio Making HTTP Requests Slower?

我正在使用 Asyncio 和 Requests 对一系列 HTTP 请求进行基准测试。

出于某种原因,使用 Asyncio 比直接请求要慢一些。知道为什么吗?我是否错误地使用了 Asyncio?

import asyncio
import functools
import requests
import time

ts = time.time()
for i in range(10):
  @asyncio.coroutine
  def do_checks():
      loop = asyncio.get_event_loop()
      req = loop.run_in_executor(None, functools.partial(requests.get, "http://google.com", timeout=3))
      resp = yield from req
      print(resp.status_code)

  loop = asyncio.get_event_loop()
  loop.run_until_complete(do_checks())
te = time.time()
print("Version A: " + str(te - ts))

ts = time.time()
for i in range(10):
  r = requests.get("http://google.com", timeout=3)
  print(r.status_code)
te = time.time()

print("Version B:  " + str(te - ts))

输出:

版本 A = Asyncio;版本 B = 请求

200
200
200
200
200
200
200
200
200
200
Version A: 5.7215821743011475
200
200
200
200
200
200
200
200
200
200
Version B:  5.320340156555176

您正在等待每个请求完成,然后再开始下一个请求。所以你有事件循环的开销,没有任何好处。

试试这个:

import asyncio
import functools
import requests
import time

ts = time.time()
loop = asyncio.get_event_loop()

@asyncio.coroutine
def do_checks():
    futures = []
    for i in range(10):
        futures.append(loop.run_in_executor(None, functools.partial(requests.get, "http://google.com", timeout=3)))

    for req in asyncio.as_completed(futures):
        resp = yield from req
        print(resp.status_code)

loop.run_until_complete(do_checks())
te = time.time()
print("Version A: " + str(te - ts))

ts = time.time()
for i in range(10):
    r = requests.get("http://google.com", timeout=3)
    print(r.status_code)
te = time.time()
print("Version B:  " + str(te - ts))

这是我 运行 它得到的结果:

$ python test.py 
200
...
Version A: 0.43438172340393066
200
...
Version B: 1.6541109085083008

快得多,但实际上这只是生成线程并等待 http 库完成,您不需要 asyncio 来完成。

您可能想要查看 aiohttp,因为它是为与 asyncio 一起使用而构建的。 requests 是一个很棒的库,但它不是为 asyncio 而设计的。

为了完整起见,这是一个非常快速的 asyncio 实现

import aiohttp
import asyncio
import time

async def main(n):
    ts = time.time()
    session = aiohttp.ClientSession()
    fs = [session.get('http://google.com') for _ in range(n)]
    for f in asyncio.as_completed(fs):
        resp = await f
        print(resp.status)
        await resp.release()
    await session.close()
    te = time.time()
    print("Aiohttp version:  " + str(te - ts))

loop = asyncio.get_event_loop()
loop.run_until_complete(main(10))
loop.close()

代码是 python 3.5 及更高版本。

~> python asyncioreq.py
200
...
Aiohttp version:  0.15974688529968262

希望有人可以使用它;)

基于@brunsgaard 的回答:您可以使用 aiohttp 更进一步并使用 asyncio.gather() 收集结果。然后您可以从您的请求中获取响应并处理它们。

import aiohttp
import asyncio
import time

async def main(n):
    start = time.time()
    session = aiohttp.ClientSession()
    jobs = [session.get('http://google.com') for _ in range(n)]
    done_jobs = await asyncio.gather(*jobs)
    for done_job in done_jobs:
        print(done_job.status)
    session.close()
    end = time.time()
    print("Time:  " + str(end - start))

loop = asyncio.get_event_loop()
loop.run_until_complete(main(10))
loop.close()