之前一直没有仔细研究过python的并发方案,实习的时候终于有了这样的需求。

Gevent是Python中coroutine协程的一种实现,基于Greenlet。之前我只是听说过其在性能优化上简单而有效,正好最近有并发需要,所以就稍微用了一下。

确实是非常简单,其中的monkey库可以直接patch系统的多进程、多线程还有socket的实现,具体的调用方法就是monkey.patch_xxx(),xxx可以是socket,ssl,os,subprocess,thread等。还可以直接调用monkey.patch_all()执行所有的patch。

gevent的好处在于可以自动切换协程,一般来说I/O操作比较耗时,那么当一个协程在等待I/O时,它会自动切换到其他协程工作,这样就能够充分利用CPU了。

比较常用的函数:

  • gevent.spawn(func, *args, **kwargs) :创建greenlet对象
  • gevent.joinall(greenlets) :传入一个greenlet对象列表,开始所有任务并等待完成
  • gevent.sleep(seconds) :挂起secodes秒
  • gevent.kill / killall:杀死某个greenlet或一个list的greenlet
  • gevent.wait(objects=None, timeout=None, count=None):进入等待

基本的使用方法与多线程类似,这里给出一个小例子,是我实际写出的一个并发爬虫的脚本(当然爬取的代码还没有),每个协程完成某一段区间的任务。

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
26
from gevent import monkey
monkey.patch_all()
import gevent
import urllib2

total_start = 0
total_end = 100000
workload = 400


def generate_tasklist():
start_list = range(total_start, total_end, workload)
return map(lambda x, y: (x, y-1), start_list, start_list[1:] + [total_end+1])


def fronter(id, since, end):
# some code to crawl
print '[%d] Starting ...' % id
response = urllib2.urlopen('https://github.com')
print '[%d] Finish with %d' % response.getcode()

if __name__ == '__main__':
task_list = generate_tasklist()
task_list = map(lambda x: gevent.spawn(fronter, x[0], *x[1]), enumerate(task_list))
print '[+] Total workers: %d' % len(task_list)
gevent.joinall(task_list)

确实使用起来比较简单,如果需要主动放弃CPU,可以调用gevent.wait()。

更多高级的使用技巧可以研究官方文档:Gevent Docs

一份非官方的教程:Gevent Tutorial