Python Gevent应用

Gevent介绍

看了下gevent,最好的资料还是gevent程序员指南,亲自用了下gevent,因为是第一次真正接触coroutine,所以还是有点抽象,碰到了一些问题。

关于coroutine

淺談coroutine與gevent

gevent应用

场景:有100个url,要对这100个url并发发起请求,通过个url丢进去,然后执行完成后,100个url请求的结果独立返回给我,如果是true那么就是返回请求的结果,如果是false,那么返回错误的原因。

测试一

下面是写的第一个版本。

#!/usr/bin/env python
#coding=utf-8

import gevent
import urllib2
import time

def fetch(pid, url):
    print('Process %s: %s start work' % (pid, url))
    flag = None
    status = None
    error_msg = None
    try :
        req_obj = urllib2.Request(url)
        response = urllib2.urlopen(req_obj, timeout=10)
        status = response.code
        result = response.read()
        flag = True
    except Exception, e :
        error_msg = str(e)
        flag = False

    print('Process %s: %s %s' % (pid, url, status))
    return (pid, flag, url, status, error_msg)

def asynchronous():
    i = 0
    jobs = []
    url = 'http://107.167.184.223/'
    for i in range(0, 100) : 
        jobs.append(gevent.spawn(fetch, i, url))
    gevent.joinall(jobs)
    value = []
    for job in jobs:
 #      print job.value
        value.append(job.value)

start = time.time()
print('Asynchronous:')
asynchronous()
stop = time.time()
print stop-start

执行结果: 测试的过程中发现实际执行起来还是串行的,并没有出现各个coroutine一旦阻塞就返回控制权给主coroutine,然后主coroutine调度下一个。

Process 0: http://107.167.184.223/ start work
Process 0: http://107.167.184.223/ 200
Process 1: http://107.167.184.223/ start work
Process 1: http://107.167.184.223/ 200
...
Process 99: http://107.167.184.223/ start work
Process 99: http://107.167.184.223/ 200
198.443364859

问题分析: 仔细想一些,一个子coroutine进行阻塞调用的时候,主coroutine怎么发现得了,因为子coroutine调用的都是python自身的库从而去调用系统调用。

测试二(gevent.sleep)

怎么解决上面的问题,查看官网的demo,可以通过加gevent.sleep(0)来显示"通知"主coroutine当前的coroutine已经block了,你可以调度其它coroutine了。修改下fetch()的代码。

def fetch(pid, url):
    ...
    try :
        req_obj = urllib2.Request(url)
        gevent.sleep(0) # 添加这句
        response = urllib2.urlopen(req_obj, timeout=10)
        status = response.code
        ...

执行结果: 发现coroutine倒是一次性全都起来了,但是在做urlopen的过程还是串行的,效率还是很差。

Process 0: http://107.167.184.223/ start work
Process 1: http://107.167.184.223/ start work
...
Process 99: http://107.167.184.223/ start work
Process 0: http://107.167.184.223/ 200
Process 1: http://107.167.184.223/ 200
...
Process 99: http://107.167.184.223/ 200
112.656867981

问题分析: 问题同上面,主coroutine虽然识别了gevent.sleep,但是urlopen的socket还是阻塞的,所以urlopen相当于是阻塞着一个一个串行进行。这个例子和《gevent程序员指南》上有本质的区别,因为它是调用gevent.sleep去模拟事件处理过程的。

import gevent
import random

def task(pid):
    """
    Some non-deterministic task
    """
    gevent.sleep(random.randint(0,2)*0.001)
    print('Task %s done' % pid)

def synchronous():
    for i in range(1,10):
        task(i)

def asynchronous():
    threads = [gevent.spawn(task, i) for i in xrange(10)]
    gevent.joinall(threads)

print('Synchronous:')
synchronous()

print('Asynchronous:')
asynchronous()

测试三(monkey patch)

为了解决测试二中的问题,就是打gevent的monkey patch,这个patch针对python几个阻塞的库都做了patch(socket,DNS,select,thread,httplib等),打了money patch之后,这些库都会变成非阻塞,然后统一由gevent的主coroutine调度,一旦这些库碰到阻塞就会把控制权返回给主coroutine。只需要在最开始打个patch就行, fetch()中的gevent.sleep(0)不再需要。

import gevent.monkey
gevent.monkey.patch_socket()

import gevent
import urllib2
import time
...
...

执行结果: 100个子coroutine都同时起来了,而且socket也是非阻塞的,主coroutine不断切换,效率大大提高。

Process 0: http://107.167.184.223/ start work
Process 1: http://107.167.184.223/ start work
...
Process 99: http://107.167.184.223/ start work
Process 62: http://107.167.184.223/ 200
Process 40: http://107.167.184.223/ 200
...
Process 33: http://107.167.184.223/ 200
Process 59: http://107.167.184.223/ 200
Process 17: http://107.167.184.223/ 200
13.1921060085

总结

gevent打了monkey patch之后会设置python相应的模块设置成非阻塞,然后在内部实现epoll的机制,一旦某一个套接字调用非阻塞的IO(比如recv)都会理解返回,并且设置一个回调函数,这个回调函数是用于切换到当前子coroutine,设置好回掉函数之后就把控制权返回给主coroutine,主coroutine继续调度。一旦网络I/O准备就绪,epoll会触发之前设置的回调函数,从而引发主coroutine切换到子coroutine,做相应的操作。

标签:Python, Gevent

评论已关闭