服务器端的阻塞和非阻塞调用,为什么对异步客户端很重要?

Blocking and non-blocking calls on server side, why does it matter for asynchronous client side?

在 Python 3.8.0 中试验一些异步代码时,我偶然发现了以下情况。我有 client.py,它可以与 server.py 中的服务器异步处理连接。这个服务器假装在做一些工作,但实际上睡了几秒钟然后 returns。我的问题是,由于服务器 运行 在一个完全不同的进程中,为什么睡眠方法是否阻塞很重要,如果服务器端的进程可能没有阻塞,那么异步的好处是什么首先是这样的电话?

# client.py

import time
import asyncio

import aiohttp


async def request_coro(url, session):
    async with session.get(url) as response:
        return await response.read()


async def concurrent_requests(number, url='http://localhost:8080'):
    tasks = []
    async with aiohttp.ClientSession() as session:
        for n in range(number):
            # Schedule the tasks
            task = asyncio.create_task(request_coro(url, session))
            tasks.append(task)

        # returns when all tasks are completed
        return await asyncio.gather(*tasks)


t0 = time.time()
responses = asyncio.run(concurrent_requests(10))
elapsed_concurrent = time.time() - t0

sum_sleeps = sum((int(i) for i in responses))
print(f'{elapsed_concurrent=:.2f} and {sum_sleeps=:.2f}')
# server.py

import time
import random
import logging
import asyncio

from aiohttp import web


random.seed(10)


async def index(requests):
    # Introduce some latency at the server side
    sleeps = random.randint(1, 3)

    # NON-BLOCKING
    # await asyncio.sleep(sleeps)

    # BLOCKING
    time.sleep(sleeps)

    return web.Response(text=str(sleeps))


app = web.Application()
app.add_routes([web.get('/', index),
                web.get('/index', index)])


logging.basicConfig(level=logging.DEBUG)
web.run_app(app, host='localhost', port=8080)

这些是客户端使用阻塞或非阻塞睡眠方法进行 10 次异步调用的结果:

asyncio.sleep(非阻塞)

elapsed_concurrent=3.02 和 sum_sleeps=19.00

time.sleep(阻塞)

elapsed_concurrent=19.04 和 sum_sleeps=19.00

虽然服务器运行处于完全不同的进程中,但它不能同时占用多个活动连接,就像多线程服务器一样。所以客户端和服务器异步工作,都有自己的事件循环。

当事件循环处于非阻塞睡眠状态时,服务器只能从客户端获取新连接。看起来服务器是多线程的,但实际上在可用连接之间快速切换。阻塞睡眠将使请求顺序进行,因为挂起的事件循环将处于空闲状态并且无法同时处理新连接。