????因?yàn)閜ython線程的性能問題,在python中使用多線程運(yùn)行代碼經(jīng)常不能達(dá)到預(yù)期的效果。而有些時(shí)候我們的邏輯中又需要開更高的并發(fā),或者簡單的說,就是讓我們的代碼跑的更快,在同樣時(shí)間內(nèi)執(zhí)行更多的有效邏輯、減少無用的等待。gevent就是一個(gè)現(xiàn)在很火、支持也很全面的python第三方協(xié)程庫。
????gevent是python的一個(gè)并發(fā)框架,以微線程greenlet為核心,使用了epoll事件監(jiān)聽機(jī)制以及諸多其他優(yōu)化而變得高效。而且其中有個(gè)monkey類,將現(xiàn)有基于Python線程直接轉(zhuǎn)化為greenlet(類似于打patch)。在運(yùn)行時(shí)的具體流程大概就是:
????當(dāng)一個(gè)greenlet遇到IO操作時(shí),比如訪問網(wǎng)絡(luò)/睡眠等待,就自動(dòng)切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來繼續(xù)執(zhí)行。由于IO操作非常耗時(shí),經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動(dòng)切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行,而不是等待IO。同時(shí)也因?yàn)橹挥幸粋€(gè)線程在執(zhí)行,會(huì)極大的減少上下文切換的成本。
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])
運(yùn)行后輸出如下圖所示:

????由圖中可以看出,f1和f2是交叉打印信息的,因?yàn)樵诖a執(zhí)行的過程中,我們?nèi)藶槭褂胓event.sleep(0)創(chuàng)建了一個(gè)阻塞,gevent在運(yùn)行到這里時(shí)就會(huì)自動(dòng)切換函數(shù)切換函數(shù)。也可以在執(zhí)行的時(shí)候sleep更長時(shí)間,可以發(fā)現(xiàn)兩個(gè)函數(shù)基本是同時(shí)運(yùn)行然后各自等待。
????在實(shí)際運(yùn)用的過程中,我們?nèi)绻行枰ㄟ^人為sleep來增加時(shí)間間隔或者確保部分邏輯安全的時(shí)候,此處使用就很方便了。當(dāng)然,更多時(shí)候我們還是在需要進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候使用gevent:
# -*- 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/'),
])
運(yùn)行上述代碼,結(jié)果如下:

由上圖可以看出,程序基本在同一時(shí)間觸發(fā)了對(duì)三個(gè)網(wǎng)站的請(qǐng)求,然后各自進(jìn)行,分別結(jié)束。也就是當(dāng)gevent發(fā)現(xiàn)阻塞之后,讓當(dāng)前急需執(zhí)行,然后自動(dòng)切換到了另外的請(qǐng)求中運(yùn)行。
加鎖
如果需要在使用gevent的時(shí)候加鎖,也是非常方便的:
# -*- 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])
運(yùn)行結(jié)果如下:

由輸出可以發(fā)現(xiàn),程序會(huì)同時(shí)判斷是否在sleep以及是否有鎖兩種情況,然后執(zhí)行當(dāng)前的最有操作。
小結(jié)
????gevent的優(yōu)勢不僅僅是在代碼中調(diào)用方便,厲害的是它擁有的monkey機(jī)制。假設(shè)你不愿意修改原來已經(jīng)寫好的python代碼,但是又想充分利用gevent機(jī)制,那么你就可以用monkey來做到這一點(diǎn)。你所要做的就是在文件開頭打一個(gè)patch,那么它就會(huì)自動(dòng)替換你原來的thread、socket、time、multiprocessing等代碼,全部變成gevent框架。這一切都是由gevent自動(dòng)完成的。注意這個(gè)patch是在所有module都import了之后再打,否則沒有效果。
????甚至在編寫的Web App代碼的時(shí)候,不需要引入gevent的包,也不需要改任何代碼,僅僅在部署的時(shí)候,用一個(gè)支持gevent的WSGI服務(wù)器,就可以獲得數(shù)倍的性能提升。
本文簡單介紹了gevent的使用,下一篇將對(duì)gevent的部分源碼進(jìn)行分析。