Python可以使用线程运行多个函数,使得这些函数看上去好像是在同一时间得到执行。然而,线程有三个显著的缺点:
- 为了确保数据安全,我们必须使用特殊的工具来协调这些线程。便使得线程代码变得难于扩展和维护;
- 线程需要占用大量内存,每个正在执行的线程,大约占据
8MB
的内存,如果在程序中运行上万的函数线程时,会导致计算机内存无法承受; - 线程启动时的开销比较大。如果程序不停地以靠新线程来同时执行多个函数,并等待这些线程结束,那么使用线程所引发的开销,就会拖慢整个程序的执行速度。
协程定义
Python的协程可以解决上述问题,它使得Python程序看上去好像是同时运行多个函数。协程的实现方式,实际上是对生成器(generator
)的一种扩展。同时启动协程所需的开销,与调用函数的开销相仿。
协程的工作原理
原理
:每当生成器函数执行到yield
表达式的时候,消耗生成器的那段代码,就通过send
方法给生成器回传一个值。而生成器在收到经由send
函数所传进来的这个值后,会将其视为yield
表达式的执行结果。
def my_coroutine():
while True:
received = yield
print("Received:",received)
it = my_coroutine()
next(it) ###调用一次next函数,使得运行到yield语句处
it.send('First')
it.send('Second')
输出结果:
Received:First
Received:Second
案例
:编写一个生成器协程,一次发送多个数值,给出当前统计到的最小值。
def minimize():
current = yield
while True:
value = yield current
current = min(current,value)
it = minimize()
next(it)
print(it.send(10)) #10赋给了current
print(it.send(4))
print(it.send(22))
print(it.send(-4))
生成器函数似乎一直运行,每次调用send
之后,都会产生新值赋给value
。协程会在生成器函数中每个yield
表达式那里暂停,等到外界再次调用send
方法之后,将send
的值返回给value
,同时继续执行到下一个yield
表达式。
注意:yield
语句的使用,x = yield y
表明yield
语句返回数值y
,接收数值x
,即由send
方法传进来的x
,而y
则作为返回值返回给调用者。
协程的使用
最后,来看一个生成者-消费者模型。传统的生产者-消费者模型,是一个线程写消息,一个线程读消息,通过锁机制控制队列和等待,但是容易导致死锁。
协程实现的生产者-消费者模型。
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
可以看出,生产者produce
生产消息后,直接通过send
方法跳转到消费者consumer
开始执行,待消费者consumer
执行完毕到下一个yield
处,切换回生产者produce
继续生产,效率极高。
执行流程:
- 首先调用
c.send(None)
启动生成器; - 然后,一旦生产了东西,通过
c.send(n)
切换到consumer
执行; consumer
通过yield
拿到消息,处理,又通过yield
把结果传回;produce
拿到consumer
处理的结果,继续生产下一条消息;produce
决定不生产了,通过c.close()
关闭consumer
,整个过程结束。
整个流程无锁,由一个线程执行,produce
和consumer
协作完成任务,所以称为“协程”,而非线程的抢占式多任务。