服务器端的阻塞和非阻塞调用,为什么对异步客户端很重要?
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
虽然服务器运行处于完全不同的进程中,但它不能同时占用多个活动连接,就像多线程服务器一样。所以客户端和服务器异步工作,都有自己的事件循环。
当事件循环处于非阻塞睡眠状态时,服务器只能从客户端获取新连接。看起来服务器是多线程的,但实际上在可用连接之间快速切换。阻塞睡眠将使请求顺序进行,因为挂起的事件循环将处于空闲状态并且无法同时处理新连接。
在 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
虽然服务器运行处于完全不同的进程中,但它不能同时占用多个活动连接,就像多线程服务器一样。所以客户端和服务器异步工作,都有自己的事件循环。
当事件循环处于非阻塞睡眠状态时,服务器只能从客户端获取新连接。看起来服务器是多线程的,但实际上在可用连接之间快速切换。阻塞睡眠将使请求顺序进行,因为挂起的事件循环将处于空闲状态并且无法同时处理新连接。