You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@coroutinedefroutine_ur(url, wait):
yieldsleep(wait)
print('routine_ur {} took {}s to get!'.format(url, wait))
@coroutinedefroutine_url_with_return(url, wait):
yieldsleep(wait)
print('routine_url_with_return {} took {}s to get!'.format(url, wait))
raiseReturn((url, wait))
# 非生成器协程,不会为之生成单独的 Runner()# coroutine 运行结束后,直接返回一个已经执行结束的 future@coroutinedefroutine_simple():
print("it is simple routine")
@coroutinedefroutine_simple_return():
print("it is simple routine with return")
raiseReturn("value from routine_simple_return")
@coroutinedefroutine_main():
yieldroutine_simple()
yieldroutine_ur("url0", 1)
ret=yieldroutine_simple_return()
print(ret)
ret=yieldroutine_url_with_return("url1", 1)
print(ret)
ret=yieldroutine_url_with_return("url2", 2)
print(ret)
if__name__=='__main__':
IOLoop.instance().run_sync(routine_main)
运行输出为:
it is simple routine
routine_ur url0 took 1s to get!
it is simple routine with return
value from routine_simple_return
routine_url_with_return url1 took 1s to get!
('url1', 1)
routine_url_with_return url2 took 2s to get!
('url2', 2)
tornado 源码之 coroutine 分析
为支持异步,tornado 实现了一个协程库。
tornado 实现的协程框架有下面几个特点:
特性,纯粹使用 yield 实现
由此可见,这是 python 协程的一个经典的实现。
本文将实现一个类似 tornado 实现的基础协程框架,并阐述相应的原理。
外部库
使用 time 来实现定时器回调的时间计算。
bisect 的 insort 方法维护一个时间有限的定时器队列。
functools 的 partial 方法绑定函数部分参数。
使用 backports_abc 导入 Generator 来判断函数是否是生成器。
Future
add_done_callback 注册回调函数,当 Future 被解决时,改回调函数被调用。
set_result 设置最终的状态,并且调用已注册的回调函数
协程中的每一个 yield 对应一个协程,相应的对应一个 Future 对象,譬如:
这里的 routine_simple() 和 sleep(1) 分别对应一个协程,同时有一个 Future 对应。
IOLoop
这里的 IOLoop 去掉了 tornado 源代码中 IO 相关部分,只保留了基本需要的功能,如果命名为 CoroutineLoop 更贴切。
这里的 IOLoop 提供基本的回调功能。它是一个线程循环,在循环中完成两件事:
程序中注册的回调事件,最终都会在此处执行。
可以认为,协程程序本身、协程的驱动程序 都会在此处执行。
协程本身使用 wrapper 包装,并最后注册到 IOLoop 的事件回调,所以它的从预激到结束的代码全部在 IOLoop 回调中执行。
而协程预激后,会把 Runner.run() 函数注册到 IOLoop 的事件回调,以驱动协程向前运行。
理解这一点对于理解协程的运行原理至关重要。
这就是单线程异步的基本原理。因为都在一个线程循环中执行,我们可以不用处理多线程需要面对的各种繁琐的事情。
IOLoop.start
事件循环,回调事件和定时器事件在循环中调用。
IOLoop.run_sync
执行一个协程。
将 run 注册进全局回调,在 run 中调用 func()启动协程。
注册协程结束回调 stop, 退出 run_sync 的 start 循环,事件循环随之结束。
coroutine
协程装饰器。
协程由 coroutine 装饰,分为两类:
装饰协程,并通过注册回调驱动协程运行。
程序中通过 yield coroutine_func() 方式调用协程。
此时,wrapper 函数被调用:
协程返回 Future 对象,供外层的协程处理。外部通过操作该 Future 控制协程的运行。
每个 yield 对应一个协程,每个协程拥有一个 Future 对象。
外部协程获取到内部协程的 Future 对象,如果内部协程尚未结束,将 Runner.run() 方法注册到 内部协程的 Future 的结束回调。
这样,在内部协程结束时,会调用注册的 run() 方法,从而驱动外部协程向前执行。
各个协程通过 Future 形成一个链式回调关系。
Runner 类在下面单独小节描述。
协程返回值
因为没有使用 yield from,协程无法直接返回值,所以使用抛出异常的方式返回。
python 2 无法在生成器中使用 return 语句。但是生成器中抛出的异常可以在外部 send() 语句中捕获。
所以,使用抛出异常的方式,将返回值存储在异常的 value 属性中,抛出。外部使用诸如:
这样的方式获取协程的返回值。
Runner
Runner 是协程的驱动器类。
self.result_future 保存当前协程的状态。
self.future 保存 yield 子协程传递回来的协程状态。
从子协程的 future 获取协程运行结果 send 给当前协程,以驱动协程向前执行。
注意,会判断子协程返回的 future
如果 future 已经 set_result,代表子协程运行结束,回到 while Ture 循环,继续往下执行下一个 send;
如果 future 未 set_result,代表子协程运行未结束,将 self.run 注册到子协程结束的回调,这样,子协程结束时会调用 self.run,重新驱动协程执行。
如果本协程 send() 执行过程中,捕获到 StopIteration 或者 Return 异常,说明本协程执行结束,设置 result_future 的协程返回值,此时,注册的回调函数被执行。这里的回调函数为本协程的父协程所注册的 run()。
相当于唤醒已经处于 yiled 状态的父协程,通过 IOLoop 回调 run 函数,再执行 send()。
sleep
sleep 是一个延时协程,充分展示了协程的标准实现。
流程如下图:
运行
运行输出为:
可以观察到协程 sleep 已经生效。
源码
simple_coroutine.py
copyright
author:bigfish
copyright: 许可协议 知识共享署名-非商业性使用 4.0 国际许可协议
The text was updated successfully, but these errors were encountered: