Gevent是一个基于Greenlet实现的网络库,通过Greenlet实现协程。
基本思想是一个Greenlet就认为是一个协程,当一个Greenlet遇到IO操作的时候,比如:访问网络就会自动切换到其他的Greenlet等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程就保证总有Greenlet在运行,而不是等待IO操作。
PS:Greenlet是作为一个C扩展模块,它封装了libevent事件循环的API,可以让开发者在不改变编程习惯的同时,用同步的方式写异步IO的代码。
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import gevent
def A(n):
for i in range(n):
print(gevent.getcurrent(), i)
g1 = gevent.spawn(A, 3)
g2 = gevent.spawn(A, 3)
g3 = gevent.spawn(A, 3)
g1.join()
g2.join()
g3.join()
# 结果
<Greenlet at 0x10a6eea60: A(3)> 0
<Greenlet at 0x10a6eea60: A(3)> 1
<Greenlet at 0x10a6eea60: A(3)> 2
<Greenlet at 0x10a6eed58: A(3)> 0
<Greenlet at 0x10a6eed58: A(3)> 1
<Greenlet at 0x10a6eed58: A(3)> 2
<Greenlet at 0x10a6eedf0: A(3)> 0
<Greenlet at 0x10a6eedf0: A(3)> 1
<Greenlet at 0x10a6eedf0: A(3)> 2
可以看到3个greenlet是依次运行而不是交替运行。要让greenlet交替运行,可以通过gevent.sleep()交出控制权:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import gevent
def A(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(1)
g1 = gevent.spawn(A, 3)
g2 = gevent.spawn(A, 3)
g3 = gevent.spawn(A, 3)
g1.join()
g2.join()
g3.join()
# 结果
<Greenlet at 0x10382da60: A(3)> 0
<Greenlet at 0x10382dd58: A(3)> 0
<Greenlet at 0x10382ddf0: A(3)> 0
<Greenlet at 0x10382da60: A(3)> 1
<Greenlet at 0x10382dd58: A(3)> 1
<Greenlet at 0x10382ddf0: A(3)> 1
<Greenlet at 0x10382da60: A(3)> 2
<Greenlet at 0x10382dd58: A(3)> 2
<Greenlet at 0x10382ddf0: A(3)> 2
在实际的代码中我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时gevent会自动完成,所以gevent需要将Python自带的一些标准库的运行方式由阻塞式调用变为协作式运行。这一过程在启动时通过monkey patch完成:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
from gevent import monkey; monkey.patch_all()
import requests
import gevent
def A(url):
print('Get: %s' % url)
response = requests.get(url)
content = response.text
print('%d bytes received from %s.' % (len(content), url))
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(A, 'http://httpbin.org/ip'),
gevent.spawn(A, 'http://httpbin.org/uuid'),
gevent.spawn(A, 'http://httpbin.org/user-agent')
])
3个网络操作是并发执行的,而且结束顺序不同,但只有一个线程。
在实际项目中可以使用协程异步的读写网络、读写文件、渲染界面等,而在等待协程完成的同时,CPU还可以进行其他的计算,协程的作用正在于此。
多线程的切换需要靠操作系统来完成,当线程越来越多时切换的成本会很高,而协程是在一个线程内切换的,切换过程由我们自己控制,因此开销小很多,这就是协程和多线程的根本差异。