asyncio系列之sleep()实现
先来看个例子,自己实现的模拟耗时操作
例1
import types
import select
import time
import socket
import functools
class Future:
def __init__(self, *, loop=None):
self._result = None
self._callbacks = []
self._loop = loop
def set_result(self, result):
self._result = result
callbacks = self._callbacks[:]
self._callbacks = []
for callback in callbacks:
loop._ready.append(callback)
def add_callback(self, callback):
self._callbacks.append(callback)
def __iter__(self):
print('enter Future ...')
print('foo 挂起在yield处 ')
yield self
print('foo 恢复执行')
print('exit Future ...')
return 'future'
__await__ = __iter__
class Task:
def __init__(self, cor, *, loop=None):
self.cor = cor
self._loop = loop
def _step(self):
cor = self.cor
try:
result = cor.send(None)
# 1. cor 协程执行完毕时,会抛出StopIteration,说明cor执行完毕了,这是关闭loop
except StopIteration as e:
self._loop.close()
# 2. 有异常时
except Exception as e:
"""处理异常逻辑"""
# 3. result为Future对象时
else:
if isinstance(result, Future):
result.add_callback(self._wakeup)
def _wakeup(self):
self._step()
class Loop:
def __init__(self):
self._stop = False
self._ready = []
self._scheduled = []
self._time = lambda: time.time()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
self._select = functools.partial(select.select, [sock], [], [])
def create_task(self, cor):
task = Task(cor, loop=self)
self._ready.append(task._step)
return task
def call_later(self, delay, callback, *args):
callback._when = delay
self._scheduled.append((callback, *args))
def run_until_complete(self, task):
assert isinstance(task, Task)
timeout = None
while not self._stop:
if self._ready:
timeout = 0
if self._scheduled:
callback, *args = self._scheduled.pop()
timeout = callback._when
self._ready.append(functools.partial(callback, *args))
# 通过select(timeout)来控制阻塞时间
self._select(timeout)
n = len(self._ready)
for i in range(n):
step = self._ready.pop()
step()
def close(self):
self._stop = True
@types.coroutine
def _sleep():
yield
# 自己实现一个sleep协程
async def sleep(s, result=None):
if s <= 0:
await _sleep()
return result
else:
future = Future(loop=loop)
future._loop.call_later(s, callback, future)
await future
return result
# 延迟回调函数
def callback(future):
# 时间到了就回调此函数
future.set_result(None)
async def foo():
print(f'enter foo at {time.strftime("%Y-%m-%d %H:%M:%S")}')
await sleep(3)
print(f'exit foo at {time.strftime("%Y-%m-%d %H:%M:%S")}')
if __name__ == '__main__':
f = foo()
loop = Loop()
task = loop.create_task(f)
loop.run_until_complete(task)
执行结果:
enter foo at 2019-07-08 21:09:43
enter Future ...
foo 挂起在yield处
foo 恢复执行
exit Future ...
exit foo at 2019-07-08 21:09:46
在上一篇文章通过Loop, Task, Future三个类基本上实现了对协程的调度,在此基础上做了一些修改实现了对协程中耗时操作的模拟。
首先我们分析一下async def foo协程中的await sleep(3),这里其实会进入到sleep中 await future这里,再进入到future对象的__await__方法中的yield self,foo协程此时被挂起,上一篇文章中我们分析知道,最终foo还是被这个future对象给分成了part1和part2两部分逻辑。
- foo print('enter foo at ...')
- sleep
- future print('enter Future ...') # 以上是第一次f.send(None)执行的逻辑,命名为part1
- future yield self ---------------------------------------------------------------
- print('exit Future ...') #以下是第二次f.send(None)执行的逻辑,命名为part2
- sleep
- foo print('exit foo at ...')
part1 在 loop 循环的开始就执行了,返回一个 future 对象,把 part2 注册到 future 中,然后挂起了,下半部分 part2 在什么时候执行呢?因为在 sleep 中我们通过注册了一个3秒之后执行的回调函数 callback 到 loop 对象中,loop 对象在执行完 part1 后,会在下一轮的循环中执行 callback 回调函数,由于 loop._scheduled 不为空,timeout 被赋值成3,因此 select(3) 阻塞3秒后就继续往下执行。也就是说 callback 函数的执行时机就是在 select(3) 阻塞3秒后执行,callback 回调函数中又会调用 future.set_result() ,在 set_result 中会把 part2 注册到 loop 中,所以最终又在 loop 的下一轮循环中调用 part2 的逻辑,回到上次 foo 挂起的地方,继续 foo 的流程,直到协程退出。
其实所谓的模拟耗时3秒,其实就是在执行完part1后通过 select 函数阻塞3秒,然后再次执行 part2 ,这样就实现了所谓的等待3秒的操作。
要实现这个sleep协程的耗时模拟,主要是有2个关键点:
1.通过 select(timeout) 的 timeout来控制 select 函数的阻塞时间。
timeout=None 一直阻塞,直到有真实的IO事件到来,如socket的可读可写事件
timeout=0 无论此时是否有IO事件到来,都立马返回
timeout=n 阻塞n秒,在这n秒内,只要有IO事件到来,就立马返回,否则阻塞n秒才返回
2.当延迟时间到来时,通过 callback 函数中调用 future.set_result() 方法,来驱动 part2 的执行。
了解到这里之后,我们再来看一下 asyncio 的源码
Loop类
class BaseEventLoop(events.AbstractEventLoop):
...
def __init__(self):
...
# 用来保存包裹task.step方法的handle对象的对端队列
self._ready = collections.deque()
# 用来保存包裹延迟回调函数的handle对象的二叉堆,是一个最小二叉堆
self._scheduled = []
...
def create_task(self, coro):
"""Schedule a coroutine object.
Return a task object.
"""
self._check_closed()
# self._task_factory 默认是None
if self._task_factory is None:
# 创建一个task对象
task = tasks.Task(coro, loop=self)
if task._source_traceback:
del task._source_traceback[-1]
else:
task = self._task_factory(self, coro)
# 返回这个task对象
return task
def call_soon(self, callback, *args):
self._check_closed()
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_soon')
# 关键代码callback就是task._step方法,args是task._step的参数
handle = self._call_soon(callback, args)
if handle._source_traceback:
del handle._source_traceback[-1]
return handle
def _call_soon(self, callback, args):
# 1 handle是一个包裹了task._step方法和args参数的对象
handle = events.Handle(callback, args, self)
if handle._source_traceback:
del handle._source_traceback[-1]
# 2 关键代码,把handle添加到列表self._ready中
self._ready.append(handle)
return handle
def run_until_complete(self, future):
...
# future就是task对象,下面2句是为了确保future是一个Future类实例对象
new_task = not futures.isfuture(future)
future = tasks.ensure_future(future, loop=self)
if new_task:
# An exception is raised if the future didn't complete, so there
# is no need to log the "destroy pending task" message
future._log_destroy_pending = False
# 添加回调方法_run_until_complete_cb到当前的task对象的callbacks列表中,_run_until_complete_cb就是最后
# 把loop的_stop属性设置为ture的,用来结束loop循环的
future.add_done_callback(_run_until_complete_cb)
try:
# 开启无线循环
self.run_forever()
except:
...
raise
finally:
...
# 执行完毕返回cor的返回值
return future.result()
def run_forever(self):
...
try:
events._set_running_loop(self)
while True:
# 每次运行一次循环,判断下_stopping是否为true,也就是是否结束循环
self._run_once()
if self._stopping:
break
finally:
...
def _run_once(self):
# loop的_scheduled是一个最小二叉堆,用来存放延迟执行的回调函数,根据延迟的大小,把这些回调函数构成一个最小堆,然后再每次从对顶弹出延迟最小的回调函数放入_ready双端队列中,
# loop的_ready是双端队列,所有注册到loop的回调函数,最终是要放入到这个队列中,依次取出然后执行的
# 1. self._scheduled是否为空
sched_count = len(self._scheduled)
if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
self._timer_cancelled_count / sched_count >
_MIN_CANCELLED_TIMER_HANDLES_FRACTION):
# Remove delayed calls that were cancelled if their number
# is too high
new_scheduled = []
for handle in self._scheduled:
if handle._cancelled:
handle._scheduled = False
else:
new_scheduled.append(handle)
heapq.heapify(new_scheduled)
self._scheduled = new_scheduled
self._timer_cancelled_count = 0
else:
# Remove delayed calls that were cancelled from head of queue.
while self._scheduled and self._scheduled[0]._cancelled:
self._timer_cancelled_count -= 1
handle = heapq.heappop(self._scheduled)
handle._scheduled = False
# 2. 给timeout赋值,self._scheduled为空,timeout就为None
timeout = None
# 只要self._ready不为空,timeout就为0
if self._ready or self._stopping:
timeout = 0
# 只要self._scheduled不为空
elif self._scheduled:
# Compute the desired timeout.
# 用堆顶的回调函数的延迟时间作为timeout的等待时间,也就是说用等待时间最短的回调函数的时间作为timeout的等待时间
when = self._scheduled[0]._when
timeout = max(0, when - self.time())
、
if self._debug and timeout != 0:
...
# 3. 关注else分支,这是关键代码
else:
# timeout=None --> 一直阻塞,只要有io事件产生,立马返回event_list事件列表,否则一直阻塞着
# timeout=0 --> 不阻塞,有io事件产生,就立马返回event_list事件列表,没有也返空列表
# timeout=2 --> 阻塞等待2s,在这2秒内只要有io事件产生,立马返回event_list事件列表,没有io事件就阻塞2s,然后返回空列表
event_list = self._selector.select(timeout)
# 用来处理真正的io事件的函数,sleep调用时模拟IO耗时,并不涉及IO事件
self._process_events(event_list)
# Handle 'later' callbacks that are ready.
end_time = self.time() + self._clock_resolution
# 4. 依次取出堆顶的回调函数handle添加到_ready队列中
while self._scheduled:
handle = self._scheduled[0]
# 当_scheduled[]中有多个延迟回调时,通过handle._when >= end_time来阻止没有到时间的延迟函数被弹出,
# 也就是说,当有n个延迟回调时,会产生n个timeout,对应n次run_once循环的调用
if handle._when >= end_time:
break
# 从堆中弹出堆顶最小的回调函数,放入 _ready 队列中
handle = heapq.heappop(self._scheduled)
handle._scheduled = False
self._ready.append(handle)
# 5. 执行self._ready队列中所有的回调函数handle对象
ntodo = len(self._ready)
for i in range(ntodo):
handle = self._ready.popleft()
if handle._cancelled:
continue
if self._debug:
try:
self._current_handle = handle
t0 = self.time()
handle._run()
dt = self.time() - t0
if dt >= self.slow_callback_duration:
logger.warning('Executing %s took %.3f seconds',
_format_handle(handle), dt)
finally:
self._current_handle = None
else:
# handle._run()实际上就是执行task._step(),也就是执行cor.send(None)
handle._run()
handle = None # Needed to break cycles when an exception occurs.
Task类
class Task(futures.Future):
...
def _step(self, exc=None):
"""
_step方法可以看做是task包装的coroutine对象中的代码的直到yield的前半部分逻辑
"""
...
try:
if exc is None:
# 1.关键代码,调用协程
result = coro.send(None)
else:
result = coro.throw(exc)
# 2. coro执行完毕会抛出StopIteration异常
except StopIteration as exc:
if self._must_cancel:
# Task is cancelled right before coro stops.
self._must_cancel = False
self.set_exception(futures.CancelledError())
else:
# result为None时,调用task的callbasks列表中的回调方法,在调用loop.run_until_complite,结束loop循环
self.set_result(exc.value)
except futures.CancelledError:
super().cancel() # I.e., Future.cancel(self).
except Exception as exc:
self.set_exception(exc)
except BaseException as exc:
self.set_exception(exc)
raise
# 3. result = coro.send(None)不抛出异常,说明协程被yield挂起
else:
# 4. 查看result是否含有_asyncio_future_blocking属性
blocking = getattr(result, '_asyncio_future_blocking', None)
if blocking is not None:
# Yielded Future must come from Future.__iter__().
if result._loop is not self._loop:
self._loop.call_soon(
self._step,
RuntimeError(
'Task {!r} got Future {!r} attached to a '
'different loop'.format(self, result)))
elif blocking:
if result is self:
self._loop.call_soon(
self._step,
RuntimeError(
'Task cannot await on itself: {!r}'.format(
self)))
# 4.1. 如果result是一个future对象时,blocking会被设置成true
else:
result._asyncio_future_blocking = False
# 把_wakeup回调函数设置到此future对象中,当此future对象调用set_result()方法时,就会调用_wakeup方法
result.add_done_callback(self._wakeup)
self._fut_waiter = result
if self._must_cancel:
if self._fut_waiter.cancel():
self._must_cancel = False
else:
self._loop.call_soon(
self._step,
RuntimeError(
'yield was used instead of yield from '
'in task {!r} with {!r}'.format(self, result)))
# 5. 如果result是None,则注册task._step到loop对象中去,在下一轮_run_once中被回调
elif result is None:
# Bare yield relinquishes control for one event loop iteration.
self._loop.call_soon(self._step)
# --------下面的代码可以暂时不关注了--------
elif inspect.isgenerator(result):
# Yielding a generator is just wrong.
self._loop.call_soon(
self._step,
RuntimeError(
'yield was used instead of yield from for '
'generator in task {!r} with {}'.format(
self, result)))
else:
# Yielding something else is an error.
self._loop.call_soon(
self._step,
RuntimeError(
'Task got bad yield: {!r}'.format(result)))
finally:
self.__class__._current_tasks.pop(self._loop)
self = None # Needed to break cycles when an exception occurs.
def _wakeup(self, future):
try:
future.result()
except Exception as exc:
# This may also be a cancellation.
self._step(exc)
else:
# 这里是关键代码,上次的_step()执行到第一次碰到yield的地方挂住了,此时再次执行_step(),
# 也就是再次执行 result = coro.send(None) 这句代码,也就是从上次yield的地方继续执行yield后面的逻辑
self._step()
self = None # Needed to break cycles when an exception occurs.
Future类
class Future:
...
def add_done_callback(self, fn, *, context=None):
if self._state != _PENDING:
self._loop.call_soon(fn, self, context=context)
else:
if context is None:
context = contextvars.copy_context()
self._callbacks.append((fn, context))
def set_result(self, result):
if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self))
self._result = result
self._state = _FINISHED
self.__schedule_callbacks()
def __iter__(self):
# self.done()返回False,
if not self.done():
self._asyncio_future_blocking = True
# 把Future对象自己返回出去
yield self # This tells Task to wait for completion.
assert self.done(), "yield from wasn't used with future"
return self.result() # May raise too.
if compat.PY35:
__await__ = __iter__ # make compatible with 'await' expression
sleep协程
#延迟回调函数,里面调用fut.set_result
def _set_result_unless_cancelled(fut, result):
if fut.cancelled():
return
# 关键是这一步,驱动协程从上次挂起的地方继续执行
fut.set_result(result)
@types.coroutine
def __sleep0():
yield
async def sleep(delay, result=None, *, loop=None):
"""Coroutine that completes after a given time (in seconds)."""
if delay <= 0:
await __sleep0()
return result
if loop is None:
loop = events.get_event_loop()
# 创建一个future对象
future = loop.create_future()
# 注册一个延迟回调函数到loop对象中
h = loop.call_later(delay, futures._set_result_unless_cancelled, future, result)
try:
return await future
finally:
h.cancel()
关键地方我都写了注释,如果能耐着性子细心看下来,你会发现例1中的实现,就是模仿asyncio中的这几个类去实现的。
asyncio的sleep中的延迟回调函数是_set_result_unless_cancelled与我写的callback对应,关键都是要回调future.set_result方法,这样才能驱动协程从上次挂起的地方开始继续执行。
对于使用asyncio.sleep的例子
import asyncio
async def cor():
print('enter cor ...')
await asyncio.sleep(2)
print('exit cor ...')
return 'cor'
loop = asyncio.get_event_loop()
task = loop.create_task(cor())
rst = loop.run_until_complete(task)
print(rst)
await asyncio.sleep(2) 这句代码同样是把cor协程分为如下两个部分:
- cor print('enter cor ...')
- sleep
- future print('enter Future ...') # 以上是第一次cor.send(None)执行的逻辑,命名为part1
- future yield self ---------------------------------------------------------------
- future print('exit Future ...') # 以下是第二次cor.send(None)执行的逻辑,命名为part2
- sleep
- cor print('exit foo ...')
总之,只要有要耗时的地方,就必须要有一个 future 用来 await future,然后协程就被分成了part1和part2,part1和part2就被分别封装到了task._step和task._wakeup中,然后在loop循环中先调用part1,再通过select函数阻塞n秒之后,再执行part2,最后,协程执行完毕。
asyncio系列之sleep()实现的更多相关文章
- asyncio系列之Lock实现
import types import select import time import socket import functools import collections class Futur ...
- 抽丝剥茧分析asyncio事件调度的核心原理
先来看一下一个简单的例子 例1: async def foo(): print('enter foo ...') await bar() print('exit foo ...') async def ...
- [译]Python中的异步IO:一个完整的演练
原文:Async IO in Python: A Complete Walkthrough 原文作者: Brad Solomon 原文发布时间:2019年1月16日 翻译:Tacey Wong 翻译时 ...
- Python并发编程之初识异步IO框架:asyncio 上篇(九)
大家好,并发编程 进入第九篇. 通过前两节的铺垫(关于协程的使用),今天我们终于可以来介绍我们整个系列的重点 -- asyncio. asyncio是Python 3.4版本引入的标准库,直接内置了对 ...
- Python猫荐书系列之五:Python高性能编程
稍微关心编程语言的使用趋势的人都知道,最近几年,国内最火的两种语言非 Python 与 Go 莫属,于是,隔三差五就会有人问:这两种语言谁更厉害/好找工作/高工资…… 对于编程语言的争论,就是猿界的生 ...
- Python3爬虫系列:理论+实验+爬取妹子图实战
Github: https://github.com/wangy8961/python3-concurrency-pics-02 ,欢迎star 爬虫系列: (1) 理论 Python3爬虫系列01 ...
- 关于asyncio知识(四)
一.使用 asyncio 总结 最近在公司的一些项目中开始慢慢使用python 的asyncio, 使用的过程中也是各种踩坑,遇到的问题也不少,其中有一次是内存的问题,自己也整理了遇到的问题以及解决方 ...
- python递归、collections系列以及文件操作进阶
global log 127.0.0.1 local2 daemon maxconn log 127.0.0.1 local2 info defaults log global mode http t ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
随机推荐
- 细数Windows 的那些小技巧!
以下整理自知乎 Windows 有哪些你相见恨晚的技巧?和Quora(英文版) What are some secret tricks you should know about Windows? 等 ...
- C++该typeid和dynamic_cast
1.typeid在没有虚拟函数的(不相关的动态绑定),typeid它只返回操作对象的实际类型 2.typeid涉及到动态联编问题时(使用基类指针p或者引用p操作派生类对象),typeid(p)返回基类 ...
- WPF - 模板查看工具:Show Me The Template及如何查看第三方主题
原文:WPF - 模板查看工具:Show Me The Template及如何查看第三方主题 在学习WPF的模板(DataTemplate.ItemsPanelTemplate.ControlTemp ...
- js div的显示和隐藏
<head> <title></title> <style type="text/css"> div ...
- SEED缓冲区溢出实验笔记——Return_to_libc
参考:http://www.cis.syr.edu/~wedu/seed/Labs_12.04/Software/Return_to_libc/ http://drops.wooyun.org/ ...
- 简单实用SQL脚本Part:查找SQL Server 自增ID值不连续记录
原文:简单实用SQL脚本Part:查找SQL Server 自增ID值不连续记录 在很多的时候,我们会在数据库的表中设置一个字段:ID,这个ID是一个IDENTITY,也就是说这是一个自增ID.当并发 ...
- ORA-13541: system moving window baseline size (691200) greater than retention (432000)
修改awr生成报告时间间隔和保存时间时报错,由默认的每小时生成,保存8天修改为每半个小时生成一次,保存5天: SQL, retention); , retention); END; * ERROR a ...
- windows 2003 无法安装 .net4.0 windows服务
错误: InstallUtil.InstallLog文件中的错误信息: 正在运行事务处理安装. 正在开始安装的“安装”阶段. 查看日志文件的内容以获得 D:\Debug\DataChang.e ...
- delphi Stomp客户端连接 RabbitMQ(1)
最近公司想上个消息推送系统,网上搜了很多,因公司主要产品是Delphi,我选择了开源的RabbitMQ,Erlang语言开发,天生并行. 代码下载地址:delphistomp下载地址 windows上 ...
- [机器学习]SVM原理
SVM是机器学习中神一般的存在,虽然自深度学习以来有被拉下神坛的趋势,但不得不说SVM在这个领域有着举足轻重的地位.本文从Hard SVM 到 Dual Hard SVM再引进Kernel Trick ...