前面讲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。


taowen
4.1k 声望1.4k 粉丝

Go开发者们请加入我们,滴滴出行平台技术部 taowen@didichuxing.com