前面讲generator是显式的协程的时候缺一个例子,现在补上
def parent_generator():
print('hello')
yield from sub_generator()
print('world')
def sub_generator():
yield 1
gen = parent_generator()
gen.send(None)
gen.send(None)
这里可以看出parent_generator为了在hello和world之间中断,必须显式的用yield from把控制权从自己手里转交给调用者。如果parent_generator没有使用yield,那么sub_generator里即使有yield也无法使得parent_generator的执行权转交出去。所以从视觉上可以一步了然的指导一个函数中哪些调用是产生了switch的,哪些是肯定顺序执行的。有一点类似Haskell里给所有I/O操作加类型标签的味道。
from greenlet import greenlet
def parent():
print('hello')
sub()
print('world')
def sub():
greenlet.getcurrent().parent.switch()
g = greenlet(parent)
g.switch()
print('here is switched from the sub generator')
g.switch()
这段代码的输出是
hello
here is switched from the sub generator
world
改用greenlet之后,协程之间的跳转就变得非常随意了。比如sub里可以直接把执行权交给main,而parent完全不知情。从视觉上来看parent的实现里完全不能知道在sub内部发生了switch。
虽然不能和多线程相比,但是效果是类似的。对于多线程的代码,是任何一行代码都可能与其他线程并行。类似greenlet的隐式的协程,虽然不是每一行代码都可能产生switch。虽然产生switch的地方其实和用yield写是一样多,而且也是一样固定的。但是因为缺少强制的yield,使得在不阅读被调用函数内部的实现的前提下,无法提前知道这个调用是否会产生执行权的迁移。加上协程之间有共享状态的话,一定程度上会产生类似多线程的并发读写状态的bug。