第40条 考虑协程来并发运行多个函数

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,整个过程结束。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章