对于普通的生成器,第一个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
来看例子:
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过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的一句话总结协程的特点:
“子程序就是协程的一种特例。”