關(guān)于gevent的幾點(diǎn)思考

Gevent是python的第三方庫,提供了比較完善的對(duì)協(xié)程的支持茬故。Python中GIL的存在盖灸,導(dǎo)致多線程一直不是很好用,相形之下磺芭,協(xié)程的優(yōu)勢(shì)就更加突出了赁炎。
Gevent的基本思想是:當(dāng)遇到IO操作時(shí),會(huì)自動(dòng)寫換到其他gevent钾腺,再在適當(dāng)?shù)臅r(shí)間切回來繼續(xù)執(zhí)行徙垫。這樣就減少了IO操作時(shí)的等待耗時(shí),從而能夠提高硬件資源的利用率放棒。
注:本文使用python版本2.7.12姻报, gevent版本1.2.2

1. greenlet/eventlet/gevent的關(guān)系

Greelent實(shí)現(xiàn)了一個(gè)比較易用(相比yeild)的協(xié)程切換的庫。但是greenlet沒有自己的調(diào)度過程间螟,所以一般不會(huì)直接使用吴旋。
Eventlet在Greenlet的基礎(chǔ)上實(shí)現(xiàn)了自己的GreenThread,實(shí)際上就是greenlet類的擴(kuò)展封裝厢破,而與Greenlet的不同是荣瑟,Eventlet實(shí)現(xiàn)了自己調(diào)度器稱為Hub,Hub類似于Tornado的IOLoop摩泪,是單實(shí)例的笆焰。在Hub中有一個(gè)event loop,根據(jù)不同的事件來切換到對(duì)應(yīng)的GreenThread加勤。同時(shí)Eventlet還實(shí)現(xiàn)了一系列的補(bǔ)丁來使Python標(biāo)準(zhǔn)庫中的socket等等module來支持GreenThread的切換仙辟。Eventlet的Hub可以被定制來實(shí)現(xiàn)自己調(diào)度過程。
Gevent基于libev和Greenlet鳄梅。不同于Eventlet的用python實(shí)現(xiàn)的hub調(diào)度叠国,Gevent通過Cython調(diào)用libev來實(shí)現(xiàn)一個(gè)高效的event loop調(diào)度循環(huán)。同時(shí)類似于Eventlet戴尸,Gevent也有自己的monkey_patch灭贷,在打了補(bǔ)丁后鳄逾,完全可以使用python線程的方式來無感知的使用協(xié)程炒嘲,減少了開發(fā)成本育勺。

2. gevent猴子補(bǔ)丁

猴子補(bǔ)丁monkey_patch,將標(biāo)準(zhǔn)庫中大部分的阻塞式調(diào)用替換成非阻塞的方式弊知,包括socket、ssl、threading合瓢、select、httplib等透典。通過monkey.patch_xxx()來打補(bǔ)丁晴楔。按照gevent文檔中的建議,應(yīng)該將猴子補(bǔ)丁的代碼盡可能早的被調(diào)用峭咒,這樣可以避免一些奇怪的異常税弃。
我是這樣理解的,gevent實(shí)現(xiàn)了協(xié)程的創(chuàng)建凑队、切換和調(diào)度则果,本身是同步的,而猴子補(bǔ)丁將gevent調(diào)用的阻塞庫變成非阻塞的漩氨,兩者配合實(shí)現(xiàn)了高性能的協(xié)程西壮。

3. gevent和popen

Gevent雖然提供了subprocess的支持,但是沒有提供對(duì)os.popen的支持才菠,os.system也是一樣茸时。也就是說,os.popen是阻塞的赋访。測(cè)試如下:

from gevent import monkey
monkey.patch_all()
import gevent
import os


def func(num):
    print "start", num
    os.popen("sleep 3")
    # os.system("sleep 3")
    print "end", num


g1 = gevent.spawn(func, 1)
g2 = gevent.spawn(func, 2)
g3 = gevent.spawn(func, 3)
g1.join()
g2.join()
g3.join()

說明一下可都,這里的join是用來阻塞主協(xié)程,用來做協(xié)程間同步用的蚓耽。和thread類似渠牲。
輸出結(jié)果

start 1
end 1
start 2
end 2
start 3
end 3

需要注意的是,不使用gevent時(shí)步悠, os.popen("sleep 3")本身是不阻塞的签杈,os.popen("sleep 3").read()才會(huì)阻塞。但是使用gevent時(shí)鼎兽,os.popen("sleep 3")也是會(huì)阻塞答姥。
但是使用subprocess就可以實(shí)現(xiàn)非阻塞式調(diào)用,subprocess.call和subprocess.Popen都是非阻塞的谚咬。測(cè)試如下:

from gevent import monkey
monkey.patch_all()
import gevent
import os
import subprocess

def func(num):
    print "start", num
    susubprocess.call(['sleep', '3'])
    # sub = subprocess.Popen(['sleep 3'], shell=True)
    # out, err = sub.communicate()
    print "end", num

g1 = gevent.spawn(func, 1)
g2 = gevent.spawn(func, 2)
g3 = gevent.spawn(func, 3)
g1.join()
g2.join()
g3.join()

輸出結(jié)果

start 1
start 2
start 3
end 1
end 2
end 3
4. gevent和time

Monkey.patch_all會(huì)將time庫也變成非阻塞的鹦付,也就是說monkey.patch_all之后,time.sleep等同等于gevent.sleep择卦。測(cè)試如下:

from gevent import monkey
monkey.patch_all()
import gevent
import time

def func(num):
    print "start", num
    time.sleep(3)
    print "start", num


g1 = gevent.spawn(func, 1)
g2 = gevent.spawn(func, 2)
g3 = gevent.spawn(func, 3)
g1.join()
g2.join()
g3.join()

輸出結(jié)果

start 1
start 2
start 3
end 1
end 2
end 3

當(dāng)然敲长,如果沒有monkey.patch_all或者monkey.patch_time的話郎嫁,time還是阻塞的。
可以查看patch_all的函數(shù)原型祈噪,就能知道打了哪些補(bǔ)对箢酢:

patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, httplib=False, subprocess=True, sys=False, aggressive=True, Event=False, builtins=True, signal=True)

可以看到httplib和Event默認(rèn)是關(guān)閉的,其他默認(rèn)都是開啟的辑鲤。

5. gevent和timeout

看到有文章說盔腔,gevent里使用timeout會(huì)失效,因?yàn)橐呀?jīng)是非阻塞的了遂填。
經(jīng)過驗(yàn)證铲觉,上面的說法是錯(cuò)誤的。無論使用urllib2吓坚,requests庫,timeout設(shè)置都有效灯荧。

from gevent import monkey
monkey.patch_all()
import gevent
import requests
import urllib2

def func(url):
    # print "start", url 
    # urllib2.urlopen(url, timeout=3)
    requests.get(url, timeout=3)
    # print "end", url 


g1 = gevent.spawn(func, "http://www.baidu.com")
g2 = gevent.spawn(func, "http://www.sina.com")
g3 = gevent.spawn(func, "http://www.google.com")
g1.join()
g2.join()
g3.join()

會(huì)有正常的超時(shí)報(bào)錯(cuò):

Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/gevent/greenlet.py", line 536, in run
    result = self._run(*self.args, **self.kwargs)
  File "<stdin>", line 2, in func
  File "/usr/lib/python2.7/urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python2.7/urllib2.py", line 429, in open
    response = self._open(req, data)
  File "/usr/lib/python2.7/urllib2.py", line 447, in _open
    '_open', req)
  File "/usr/lib/python2.7/urllib2.py", line 407, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 1228, in http_open
    return self.do_open(httplib.HTTPConnection, req)
  File "/usr/lib/python2.7/urllib2.py", line 1198, in do_open
    raise URLError(err)
URLError: <urlopen error [Errno 101] Network is unreachable>
Tue Jul 24 20:22:50 2018 <Greenlet at 0x7ff390f14af0: func('http://www.google.com')> failed with URLError

另外礁击,gevent里有個(gè)Timeout對(duì)象,可以很方便的實(shí)現(xiàn)非阻塞式的超時(shí)控制:

with gevent.Timeout(seconds, exception) as timeout:
     pass  # ... code block ...

如果不指定exception逗载,超時(shí)會(huì)raise timeout

6. gevent和數(shù)據(jù)庫操作

既然monkey.patch_all將socket變成非阻塞了哆窿,那么進(jìn)行數(shù)據(jù)庫操作請(qǐng)求,也會(huì)建立socket連接厉斟,自然也是非阻塞的挚躯。
比如redis:

from gevent import monkey
monkey.patch_all()
import gevent
import redis

r = redis.Redis(host="localhost",port=6379)

def func(key):
    print "start", key
    v = r.get(key)
    print "end", key


g1 = gevent.spawn(func, "a")
g2 = gevent.spawn(func, "b")
g3 = gevent.spawn(func, "c")
g1.join()
g2.join()
g3.join()

結(jié)果

start a
start b
start c
end a
end b
end c

但是MySQL是阻塞的,因?yàn)椴粱啵琈ySQL是用C寫的码荔,patch的socket補(bǔ)丁,并不生效感挥。

from gevent import monkey
monkey.patch_all()
import gevent
import MySQLdb


def func(data):
    print "start", data
    conn = MySQLdb.connect(host="localhost",user="root",passwd="root",db="test")
    cur = conn.cursor()
    cur.execute("insert into test (data) values(%s)", (data,))
    conn.commit()
    print "end", data

g1 = gevent.spawn(func, "a")
g2 = gevent.spawn(func, "b")
g3 = gevent.spawn(func, "c")
g1.join()
g2.join()
g3.join()

輸出

start a
end a
start b
end b
start c
end c
6. gevent文件IO

注意:gevent里文件IO操作是不做切換的缩搅。

from gevent import monkey
monkey.patch_all()
import gevent
import os


def func(fn):
    print "start", fn
    with open(fn, "w") as f:
        f.write("*"*100000000)
    with open(fn) as f:
        print len(f.read())
    print "end", fn


g1 = gevent.spawn(func, "text1")
g2 = gevent.spawn(func, "text2")
g3 = gevent.spawn(func, "text3")
g1.join()
g2.join()
g3.join()

結(jié)果

start text1
100000000
end text1
start text2
100000000
end text2
start text3
100000000
end text3
6. gevent的結(jié)果和異常

Gevent運(yùn)行的結(jié)果和異常可以通過value和exception來獲取触幼。需要注意的是硼瓣,協(xié)程內(nèi)部運(yùn)行的異常,不會(huì)被拋出(會(huì)被打又们)從而影響到其他協(xié)程堂鲤。

print g1.value, g1.exception
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市媒峡,隨后出現(xiàn)的幾起案子瘟栖,更是在濱河造成了極大的恐慌,老刑警劉巖丝蹭,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慢宗,死亡現(xiàn)場(chǎng)離奇詭異坪蚁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)镜沽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門敏晤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缅茉,你說我怎么就攤上這事嘴脾。” “怎么了蔬墩?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵译打,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我拇颅,道長(zhǎng)奏司,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任樟插,我火速辦了婚禮韵洋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黄锤。我一直安慰自己搪缨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布鸵熟。 她就那樣靜靜地躺著副编,像睡著了一般。 火紅的嫁衣襯著肌膚如雪流强。 梳的紋絲不亂的頭發(fā)上痹届,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音煮盼,去河邊找鬼短纵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛僵控,可吹牛的內(nèi)容都是我干的香到。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼报破,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼悠就!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起充易,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤梗脾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后盹靴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炸茧,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑞妇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梭冠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辕狰。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖控漠,靈堂內(nèi)的尸體忽然破棺而出蔓倍,到底是詐尸還是另有隱情,我是刑警寧澤盐捷,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布偶翅,位于F島的核電站,受9級(jí)特大地震影響碉渡,放射性物質(zhì)發(fā)生泄漏聚谁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一滞诺、第九天 我趴在偏房一處隱蔽的房頂上張望垦巴。 院中可真熱鬧,春花似錦铭段、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至等限,卻和暖如春爸吮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背望门。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工形娇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筹误。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓桐早,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親厨剪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哄酝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容