Python中gevent模块协程使用

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、协程池、协程锁、协程事件、协程队列。

后端开发标签