协程-Python生成器generator之next和send运行流程

对于普通的生成器,第一个next调用,相当于启动生成器,会从生成器函数的第一行代码开始执行,直到第一次执行完yield语句(第5行)后,跳出生成器函数。

然后第二个next调用,进入生成器函数后,从yield语句的下一句语句(第6行)开始执行,然后重新运行到yield语句,执行后,跳出生成器函数,

后面再次调用next,依次类推。下面是一个列子:

def consumer():
    r= 'here'
    for i in range(3):
        print("r%s: %s" %(i, r))
        yield r
        r = '200 OK ' + str(i)


c = consumer()
n1 = next(c)
print("n1: %s" %n1)
n2 = next(c)
print("n2: %s" %n2)
n3 = next(c)
print("n3: %s" %n3)

运行结果:

r0: here
n1: here
r1: 200 OK 0
n2: 200 OK 0
r2: 200 OK 1
n3: 200 OK 1


了解了next()如何让包含 yield 的函数执行后,我们再来看另外一个非常重要的函数send(msg)。其实next()和send()在一定意义上作用是相似的,区别是send()可以传递 yield 表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做c.next() 和 c.send(None) 作用是一样的。 需要提醒的是,第一次调用时,请使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有Python yield语句来接收这个值。

来看例子:
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

下面来着重说明下send执行的顺序。当第一次send(None)(对应11行)时,启动生成器,从生成器函数的第一行代码开始执行,直到第一次执行完yield(对应第4行)后,跳出生成器函数。这个过程中,n一直没有定义。

下面运行到send(1)时,进入生成器函数,注意这里与调用next的不同。这里是从第4行开始执行,把1赋值给n,但是并不执行yield部分。下面继续从yield的下一语句继续执行,然后重新运行到yield语句,执行后,跳出生成器函数。

即send和next相比,只是开始多了一次赋值的动作,其他运行流程是相同的。

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

运行结果:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

注:
上述代码(send)理解:

我理解的执行流程应该是这样的:
1.运行produce()后,开始执行到c.send(None),跳入consumer函数并执行到n = yield r停止,此时r = '';
2.然后开始produce()的第一次循环,从r = c.send(n)开始,跳入consumer函数,并把send的参数赋值给consumer()第一次循环的n,此时r = 1,n = 1.
3.因为consumer()第一次循环的n != '',因此直接print [CONSUMER] Consuming 1...,并赋200 OK给r.
4.接着开始第二次循环,执行到n = yield r停止,即最后返回r给到produce()的r变量,因此produce会print [PRODUCER] Consumer return: 200 OK。
5.循环2,3,4步骤直到第五次循环,然后手动关闭生成器。
注意到consumer函数是一个generator,把一个consumer传入produce后:

1. 首先调用c.send(None)启动生成器;

2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

3. consumer通过yield拿到消息,处理,又通过yield把结果传回;

4. produce拿到consumer处理的结果,继续生产下一条消息;

5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

最后套用Donald Knuth的一句话总结协程的特点:

“子程序就是协程的一种特例。”

附:
Python天天美味(25) - 深入理解yield

你可能感兴趣的:(Python)