python 协程

协程

与子程序(或者说函数)一样,协程(coroutine)也是一种程序组件。Donald Knuth 曾说,子程序是协程的特例

一个子程序就是一次函数调用,它只有一个入口,一次返回,调用顺序是明确的。但协程的调用和子程序则大不一样,协程允许有多个入口对程序进行中断、继续执行等操作

Python2 可以通过 yield 来实现基本的协程,但不够强大,第三方库 gevent 对协程提供了强大的支持。另外,Python3.5 提供了 async/await 语法来实现对协程的支持。

相比多线程,协程的一大特点就是它在一个线程内执行,既避免了多线程之间切换带来的开销,也避免了对共享资源的访问冲突,线程数量越多,协程的性能优势就越明显。

使用 yield 来实现简单协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time

def consumer():
message = ''
while True:
n = yield message # yield 使函数中断
if not n:
return
print '[CONSUMER] Consuming %s...' % n
time.sleep(2)
message = '200 OK'

def produce(c):
c.next() # 启动生成器
n = 0
while n < 5:
n = n + 1
print '[PRODUCER] Producing %s...' % n
r = c.send(n) # 通过 send 切换到 consumer 执行
print '[PRODUCER] Consumer return: %s' % r
c.close()

if __name__ == '__main__':
c = consumer()
produce(c)

在上面的代码中,消费者 consumer 是一个生成器函数,我们把它作为参数传给 produce,其中,next 方法用于启动生成器,send 方法用于发送消息给 consumer,并切换到 consumer 执行。consumer 通过 yield 获取到消息,然后进行处理,又通过 yield 返回消息给 produce,并转到 produce 执行,如此反复。

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[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

使用 gevent 来实现协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from gevent import monkey

monkey.patch_all()

import gevent
import requests
import time


def pr(url):
requests.get(url)


start1 = time.time()
spawns = list()
for i in range(50):
spawns.append(gevent.spawn(pr, 'https://www.baidu.com/'))
gevent.joinall(spawns)
print('协程用时: %s' % round(time.time() - start1, 2))

start2 = time.time()
for i in range(50):
pr('https://www.baidu.com/')
print('非协程用时: %s' % round(time.time() - start2, 2))

执行多次,可看出使用协程对于 IO 密集型的任务效率的提升很明显。

1
2
3
4
5
6
7
8
9
10
11
// 第一次
协程用时: 1.05
非协程用时: 38.32

// 第二次
协程用时: 1.04
非协程用时: 38.12

// 第三次
协程用时: 1.58
非协程用时: 39.04

参考

0%