python asyncio 相关整理
SelectEventLoop问题
import asyncio
import aiohttp
if __name__ == '__main__':
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def test(i):
print(i)
await fetch("https://baidu.com")
pass
tasks = []
import selectors
# 创建一个基于 SelectSelector 的事件循环
selector = selectors.SelectSelector()
loop = asyncio.SelectorEventLoop(selector=selector)
for i in range(512):
tasks.append(loop.create_task(test(i)))
loop.run_until_complete(asyncio.gather(*tasks))
上面的代码基于python 3.6
默认的事件循环 SelectEventLoop
,会出现socket耗尽的情况。
如果使用ProactorEventLoop
就没有问题。
Windows事件循环的一般限制:
- 不支持
create_unix_connection()
和create_unix_server()
:socket.AF_UNIX
套接字族只在UNIX上可用- 不支持
add_signal_handler()
和remove_signal_handler()
- 不支持
EventLoopPolicy.set_child_watcher()
。ProactorEventLoop
支持子进程。监控子进程的实现只有一种,不需要对其进行设置。特定于
SelectorEventLoop
的限制:
SelectSelector
只被用于支持套接字,并且最多支持512个套接字。add_reader()
和add_writer()
只接受套接字的文件描述符- 不支持管道(示例:
connect_read_pipe()
,connect_write_pipe()
)- 不支持 Subprocesses (示例:
subprocess_exec()
,subprocess_shell()
)特定于
ProactorEventLoop
的限制:
- 不支持
create_datagram_endpoint()
(UDP)- 不支持
add_reader()
和add_writer()
注意到SelectorEventLoop
只能使用套接字监控IO操作,最多是512个。
事件循环与协程
协程包含4个状态:挂起(Suspended)、就绪(Ready)、完成(Finished)、运行中(Running)。
事件循环只处理协程的挂起和就绪状态。
SelectorEventLoop
通过selectors.SelectSelector
来监控 I/O 事件,从而判断协程是否可以继续执行。具体步骤如下:
- 注册 I/O 事件
当协程中使用 await
等待某个 I/O 操作(例如 await asyncio.sleep()
或 await sock.recv()
)时,事件循环会将该协程挂起,并将对应的 I/O 操作注册到 SelectSelector
中。
- 监控 I/O 事件
SelectSelector
会调用 select.select()
系统调用,监控所有注册的 I/O 事件。select.select()
会阻塞,直到有 I/O 事件发生(例如套接字可读或可写)。
- 事件触发
当某个 I/O 事件发生时,SelectSelector
会返回对应的文件描述符。事件循环会根据返回的文件描述符,找到对应的协程,并将其标记为就绪。
- 执行就绪协程
事件循环会从就绪队列中取出协程,并继续执行。如果协程中还有其他 await
操作,会重复上述过程。
coroutine、task、future
coroutine可以调用coroutine,实现了一个函数暂停和恢复运行的功能
future用于表示一个同步函数或异步函数的运行结果,当函数还未完成时,获取future.result会抛出异常。
task是future的子类,它包装了协程,能返回协程的执行结果。
asyncio.sleep实现原理
# asyncio.sleep
@coroutine
def sleep(delay, result=None, *, loop=None):
"""Coroutine that completes after a given time (in seconds)."""
# 暂停0秒,也就是当前协程主动交出控制权
if delay == 0:
yield
return result
if loop is None:
loop = events.get_event_loop()
future = loop.create_future()
# 定时,delay秒后执行futures._set_result_unless_cancelled函数
h = future._loop.call_later(delay,
futures._set_result_unless_cancelled,
future, result)
try:
return (yield from future)
finally:
h.cancel()
# loop.call_later
def call_later(self, delay, callback, *args):
timer = self.call_at(self.time() + delay, callback, *args)
if timer._source_traceback:
del timer._source_traceback[-1]
return timer
# loop.call_once
def call_at(self, when, callback, *args):
self._check_closed()
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_at')
# 创建一个timer,那么要去看这个timer怎么被调用
timer = events.TimerHandle(when, callback, args, self)
if timer._source_traceback:
del timer._source_traceback[-1]
# 将timer放入等待队列
heapq.heappush(self._scheduled, timer)
timer._scheduled = True
return timer
# loop._run_once
def _run_once(self):
# 设置selector获取就绪协程的等待时间
timeout = None
if self._ready or self._stopping:
timeout = 0
elif self._scheduled:
# Compute the desired timeout.
# 取堆头第一个协程,计算最短的就绪时间
when = self._scheduled[0]._when
timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT)
if self._debug and timeout != 0:
# 不关心
pass
else:
# selector获取就绪协程
event_list = self._selector.select(timeout)
# 运行就绪协程
self._process_events(event_list)