python之gevent
因为python线程的性能问题,在python中使用多线程运行代码经常不能达到预期的效果。而有些时候我们的逻辑中又需要开更高的并发,或者简单的说,就是让我们的代码跑的更快,在同样时间内执行更多的有效逻辑、减少无用的等待。gevent就是一个现在很火、支持也很全面的python第三方协程库。
gevent是python的一个并发框架,以微线程greenlet为核心,使用了epoll事件监听机制以及诸多其他优化而变得高效。而且其中有个monkey类,将现有基于Python线程直接转化为greenlet(类似于打patch)。在运行时的具体流程大概就是:
当一个greenlet遇到IO操作时,比如访问网络/睡眠等待,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。同时也因为只有一个线程在执行,会极大的减少上下文切换的成本。
运行后输出如下图所示:
由图中可以看出,f1和f2是交叉打印信息的,因为在代码执行的过程中,我们人为使用gevent.sleep(0)创建了一个阻塞,gevent在运行到这里时就会自动切换函数切换函数。也可以在执行的时候sleep更长时间,可以发现两个函数基本是同时运行然后各自等待。
在实际运用的过程中,我们如果有需要通过人为sleep来增加时间间隔或者确保部分逻辑安全的时候,此处使用就很方便了。当然,更多时候我们还是在需要进行网络请求的时候使用gevent:
运行上述代码,结果如下:
由上图可以看出,程序基本在同一时间触发了对三个网站的请求,然后各自进行,分别结束。也就是当gevent发现阻塞之后,让当前急需执行,然后自动切换到了另外的请求中运行。
运行结果如下:
由输出可以发现,程序会同时判断是否在sleep以及是否有锁两种情况,然后执行当前的最有操作。
gevent基本使用
# -*- coding: utf-8 -*-
import gevent
def f1():
for i in range(5):
print 'run func: f1, index: %s ' % i
gevent.sleep(0)
def f2():
for i in range(5):
print 'run func: f2, index: %s ' % i
gevent.sleep(0)
t1 = gevent.spawn(f1)
t2 = gevent.spawn(f2)
gevent.joinall([t1, t2])
# -*- coding: utf-8 -*-
from gevent import monkey; monkey.patch_all()
import gevent
import requests
from datetime import datetime
def f(url):
print 'time: %s, GET: %s' % (datetime.now(), url)
resp = requests.get(url)
print 'time: %s, %d bytes received from %s.' % (
datetime.now(), len(resp.text), url)
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
加锁
如果需要在使用gevent的时候加锁,也是非常方便的:# -*- coding: utf-8 -*-
import gevent
from gevent.lock import Semaphore
sem = Semaphore(1)
def f1():
for i in range(5):
sem.acquire()
print 'run f1, this is ', i
sem.release()
gevent.sleep(1)
def f2():
for i in range(5):
sem.acquire()
print 'run f2, that is ', i
sem.release()
gevent.sleep(0.3)
t1 = gevent.spawn(f1)
t2 = gevent.spawn(f2)
gevent.joinall([t1, t2])