1. 简介
在介绍gevent模块之前,先了解一下协程的概念。协程指的是一种用户态的轻量级线程,可以由程序员自己控制进程的切换。相对于线程,协程在切换时不需要操作系统介入,因此切换速度快,占用资源少。Python中的协程实现主要有三种:Generator、asyncio、gevent。
gevent是基于Greenlet扩展的模块,它使用了libev事件库来实现协程。
关键词:协程、Greenlet、libev、事件库。
2. 安装
在使用gevent之前,需要先安装gevent模块。安装方法如下:
pip install gevent
注意:在安装时,可能会遇到一些问题。例如,需要先安装libevent-devel
参考命令:
yum -y install libevent-devel
3. 基本使用
下面通过一个简单的例子来了解gevent的基本使用。
要使用gevent实现协程,需要先导入gevent模块,然后使用gevent.monkey.patch_all()方法来实现对阻塞调用的自动协程化。下面是代码:
import gevent
from gevent import monkey
monkey.patch_all()
import time
import urllib.request
def fetch(url):
print('开始请求:', url)
response = urllib.request.urlopen(url)
print('请求结束:', url)
return response.read()
urls = [
'https://www.baidu.com/',
'https://www.cnblogs.com/',
'https://www.cnki.net/',
]
start_time = time.time()
# 串行执行
for url in urls:
res = fetch(url)
print(res)
end_time = time.time()
print('串行执行总时间:', end_time-start_time)
print('='*30)
start_time = time.time()
# 协程并发执行
tasks = [gevent.spawn(fetch, url) for url in urls]
gevent.joinall(tasks)
end_time = time.time()
print('协程并发执行总时间:', end_time-start_time)
上述代码中fetch()方法实现了请求url的方法,使用urllib.request.urlopen(url)请求url。
在执行串行请求时,依次请求urls中的url。在执行协程并发请求时,将fetch()方法使用gevent.spawn()方法生成协程对象,然后使用gevent.joinall()方法将协程对象加入到任务队列中,一起执行。
运行结果如下:
开始请求: https://www.baidu.com/
请求结束: https://www.baidu.com/
开始请求: https://www.cnblogs.com/
请求结束: https://www.cnblogs.com/
开始请求: https://www.cnki.net/
请求结束: https://www.cnki.net/
串行执行总时间: 1.3586368560791016
==============================
开始请求: https://www.baidu.com/
开始请求: https://www.cnki.net/
开始请求: https://www.cnblogs.com/
请求结束: https://www.baidu.com/
请求结束: https://www.cnki.net/
请求结束: https://www.cnblogs.com/
协程并发执行总时间: 0.39191746711730957
由上述结果可以看出,在使用协程并发执行时,三个url请求用时0.39秒。而在串行执行时,三个url请求用时1.36秒。
关键词:greenlet、monkey、gevent.spawn()、gevent.joinall()。
4. 高级使用
4.1 协程 Pool
当协程数量较多时,可以使用协程池的方式,来控制协程的数量和重用协程。gevent提供了Pool类来实现协程池。下面通过代码来了解协程池的用法。
from gevent.pool import Pool
urls = [
'https://www.baidu.com/',
'https://www.cnblogs.com/',
'https://www.cnki.net/',
]
def fetch(url):
print('开始请求:', url)
response = urllib.request.urlopen(url)
print('请求结束:', url)
return response.read()
pool = Pool(2)
tasks = [pool.spawn(fetch, url) for url in urls]
gevent.joinall(tasks)
上面的代码中,Pool(2)表示线程池的大小为2。在执行时,使用pool.spawn(fetch, url)来生成协程对象。这里将任务放到协程池中,gevent会自动管理、分配和重用协程,可以有效的控制系统资源。
关键词:Pool、pool.spawn。
4.2 协程锁
在协程中,多个协程对共享变量进行操作时,会发生竞争条件。为了避免竞争条件,需要使用协程锁。gevent提供了Lock类来实现协程锁。下面通过代码来了解协程锁的用法。
from gevent.lock import BoundedSemaphore
semaphore = BoundedSemaphore(10)
def worker(name):
with semaphore:
print('Worker %s acquired semaphore' % name)
gevent.sleep(0)
print('Worker %s released semaphore' % name)
jobs = [gevent.spawn(worker, i) for i in range(20)]
gevent.joinall(jobs)
上面的代码中,BoundedSemaphore(10)表示最多同时运行10个协程。在执行时,使用with semaphore来申请、释放协程锁。这里的worker()方法使用了gevent.sleep(0),因为如果在执行任务时不加delay,启动的协程会非常快速的完成,导致整个协程并没有实现并发。
关键词:Lock、BoundedSemaphore。
4.3 协程事件
在协程中,我们可以使用gevent.event类来实现协程之间的等待和通知机制。下面通过代码来了解协程事件的用法。
from gevent.event import Event
evt = Event()
evt.clear()
def waiter():
print('Waiting for event')
evt.wait()
print('Got event')
gevent.spawn(waiter)
gevent.sleep(1)
print('Set the event')
evt.set()
上面的代码中,创建了一个协程事件evt。在等待事件的协程中,使用evt.wait()方法来等待事件的发生。在事件发生时,使用evt.set()方法来通知协程事件的发生。在主程序中,通过gevent.sleep()等待事件等待协程启动,并在1秒后,使用evt.set()发出事件通知。
关键词:Event、evt.wait()、evt.set()。
4.4 协程队列
在协程中,我们可以使用gevent.queue类来实现协程队列,用于控制协程的调度和通信。下面通过代码来了解协程队列的用法。
from gevent.queue import Queue
queue = Queue()
def consumer(name):
while True:
product = queue.get()
if product is None:
break
print('Consumer %s got product %s' % (name, product))
print('Consumer %s quit' % name)
def producer():
for i in range(10):
queue.put('Product %s' % i)
gevent.joinall([
gevent.spawn(producer),
gevent.spawn(consumer, 'A'),
gevent.spawn(consumer, 'B'),
gevent.spawn(consumer, 'C'),
])
queue.put(None)
queue.put(None)
queue.put(None)
上面的代码中,使用Queue()方法创建协程队列,使用queue.put(product)将产品放入队列中。在消费者中使用queue.get()方法从协程队列中取出产品进行消费。在生产完成后,需要向协程队列中放入None结束任务。
关键词:Queue、queue.put()、queue.get()。
5. 总结
通过本文的学习,可以了解到gevent模块是一个非常好用的协程支持库。它基于Greenlet扩展,使用了libev事件库来实现协程,并提供了很多高级功能,如协程池、协程锁、协程事件和协程队列。
在实际使用中,需要注意不要使用阻塞调用,否则会导致协程无法切换。此外,协程任务的执行顺序是不确定的,因此需要注意相关的顺序执行问题。
关键词:协程、Greenlet、libev、协程池、协程锁、协程事件、协程队列。