玩转python(6)协程
多任务系统一般都需要解决一个问题:多个任务如何调度。抢占式调度就是一种很常见的任务调度机制。以单核模式下的进程调度为例,一个进程处于运行状态,其他的处于就绪队列,等到当前运行的进程放弃CPU的使用权,系统将CPU立刻分配给新到达的进程,由于任务的执行顺序是不确定的,看上去就像一堆任务在竞争CPU的使用权,所以这种多任务运行方式叫做“多任务竞争”。与之对应的是非抢占式调度。当前任务会持续执行下去直到因为某些原因主动放弃CPU的使用权,各个任务的执行顺序是确定的,就像在互相协作,所以这种多任务运行方式也叫做“多任务协作”。协程就是一种典型的多任务协作解决方案。协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。它的运行流程大致如下:
- 协程A开始执行。
- 协程A执行到某一步,暂停执行,执行权转移到协程B。
- 协程B执行一段时间后交还执行权。
- 协程A恢复执行。
那么生成器是如何与协程扯上关系的呢?在一个生成器中,yield item这行代码会产出一个值,提供给next()的调用方;此外,还会作出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个值时再调用next()。调用方会从生成器中拉取值。如果只能产出值,生成器显然用处不大。在python2.5之后的版本中,生成器API 中增加了send()方法。生成器的调用方可以使用send()方法发送数据,发送的数据会成为生成器函数中yield表达式的值。这样生成器既能接收数据,又能产出数据,可以作为协程使用。下面是一个简单的协程实例:
def fun():
r = ''
while True:
n = yield r
if not n:
return
print('fun test %s...' % n)
r = '200 OK'
协程在执行到n = yield r这个表达式时,首先输出r,然后将接收到的数据赋给n。然后向下执行直到遇到下一个yield语句,循环往复。接下来我们来看看这个协程是怎么工作的~
c = fun()
next(c)
c.send('1')
测试结果如下:
input:1
尽管fun()中有一个死循环,但是遇到yield,协程就会让出执行权。而且协程有个亮点,它没有涉及到上下文切换,所有的过程都是在用户空间进行的,协程的创建开销也很小,所以完全没有创建线程时的各种考虑。因此如果有多个任务,并且我们知道如何安排任务协作的情况下,协程无疑是一个好选择。