gevent學(xué)習(xí)之路

前言:最近轉(zhuǎn)技術(shù)棧,需要學(xué)習(xí)Python的gevent框架濒憋,為了能看懂怎么用DAG圖來優(yōu)化復(fù)雜并有依賴關(guān)系的初始化垫卤。我尋思這不就是Java的CompletableFuture功能嗎斧账,只不過Python對于多線程的支持不太好玻靡,所以才需要引入gevent框架著蟹。

一墩蔓、Python協(xié)程

學(xué)習(xí)gevent之前,就得先了解一下Python原生協(xié)程的支持萧豆,以及它的局限性以及不完善奸披,要不也不需要引入框架嘛。

協(xié)程與線程

線程是操作系統(tǒng)級別的調(diào)用涮雷,線程切換時需要操作系統(tǒng)阵面,保存線程的上下文。
協(xié)程程序級別的調(diào)用,協(xié)程何時需要切換样刷,是由程序員決定的仑扑。協(xié)程上下文切換的開銷更小,而且不需要解決線程安全問題置鼻。
我們可以分別用協(xié)程和線程寫消費者與生產(chǎn)者對比下:
協(xié)程實現(xiàn):

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

線程實現(xiàn)镇饮,參考這篇博客:
https://blog.csdn.net/ldx19980108/article/details/81707751
對比兩種實現(xiàn)方式,可以看出來如果用線程來實現(xiàn)箕母,那么就需要用鎖來保護(hù)共享資源盒让,但是協(xié)程就是函數(shù)執(zhí)行過程中,通過yield來暫停等待其他函數(shù)的輸入司蔬,通過send函數(shù)跑到y(tǒng)ield處執(zhí)行邑茄。開銷要小很多。當(dāng)然Python也有線程俊啼,但是受到GIL(全局解釋器鎖)的局限肺缕,也就是說在線程執(zhí)行的時候,需要獲取GIL鎖授帕,因此退化為單線程了同木,也就是說可以在多個線程中切換,但是不能多個線程同時執(zhí)行跛十,這樣的話只能并發(fā)不能并行彤路,對于多核CPU來說無疑是一種浪費。

Python 協(xié)程的實現(xiàn)

mark 晚點再補 https://zhuanlan.zhihu.com/p/45168167
1芥映、yield/send

2洲尊、select

3、yield from
yield from iterable本質(zhì)上等于for item in iterable: yield item的縮寫版

4奈偏、asyncio

5坞嘀、async/await

二、gevent的使用方式

gevent是用的比較多的Python框架惊来,以協(xié)程為核心丽涩,結(jié)合epoll的多路復(fù)用,解決了GIL鎖的問題裁蚁?矢渊?比起原生的Python來說是封裝的更好?還是存在的優(yōu)化比較多枉证?可以讓python代碼很方便的使用線程?這塊不是很理解矮男。
spawn方法注冊方法到gevent實例上,joinall函數(shù)循環(huán)事件刽严,當(dāng)遇到IO的時候昂灵,會自動切換其他事件。


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/'),
])
image.png

此外舞萄,加鎖也是十分方便的眨补。

# -*- 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])

三、greenlet的內(nèi)部原理

greenlet是gevent的基礎(chǔ)倒脓,用來實現(xiàn)從協(xié)程到協(xié)程之間的切換撑螺。切換使用的方法是switch。

from greenlet import greenlet
def test1():
    print 12
    gr2.switch()
    print 34

def test2():
    print 56
    gr1.switch()
    print 78

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

用ppt畫了個很丑陋的圖崎弃,為了是將代碼的執(zhí)行過程描述清楚甘晤,greenlet初始化的時候初始化一個空棧,當(dāng)執(zhí)行g(shù)r1.switch時候饲做,會執(zhí)行test1的代碼线婚,打印12,然后當(dāng)前協(xié)程掛起盆均,保存當(dāng)前棧和寄存器塞弊。之后執(zhí)行g(shù)r2.switch切換到另一個棧,執(zhí)行print56泪姨,然后gr1.switch的時候又切回gr1棧游沿,恢復(fù)棧和寄存器,執(zhí)行print34肮砾。


image.png

輸出結(jié)果如下所示:


image.png

從這個例子來看棧的執(zhí)行似乎诀黍,switch跟函數(shù)調(diào)用很相似。但是switch not call仗处。每個greenlet都有一個parent眯勾,greenlet的創(chuàng)生環(huán)境就是它的Parent,所有的greenlet形成一棵樹婆誓。從下面的例子可以看出來咒精。從test2返回后回到的是main,而不是test1旷档,從這也能看出來他們的區(qū)別模叙。
import greenlet
def test1(x, y):
    print id(greenlet.getcurrent()), id(greenlet.getcurrent().parent) # 40240272 40239952
    z = gr2.switch(x+y)
    print 'back z', z

def test2(u):
    print id(greenlet.getcurrent()), id(greenlet.getcurrent().parent) # 40240352 40239952
    return 'hehe'

gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
print id(greenlet.getcurrent()), id(gr1), id(gr2)     # 40239952, 40240272, 40240352
print gr1.switch("hello", " world"), 'back to main'    # hehe back to main

四、gevent源碼分析

源碼這塊功底不夠鞋屈,確實有些看不懂了范咨,先mark一下
http://www.reibang.com/p/f55148c41f54

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市厂庇,隨后出現(xiàn)的幾起案子渠啊,更是在濱河造成了極大的恐慌,老刑警劉巖权旷,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件替蛉,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機躲查,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門它浅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人镣煮,你說我怎么就攤上這事姐霍。” “怎么了典唇?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵镊折,是天一觀的道長。 經(jīng)常有香客問我介衔,道長恨胚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任炎咖,我火速辦了婚禮与纽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘塘装。我一直安慰自己急迂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布蹦肴。 她就那樣靜靜地躺著僚碎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阴幌。 梳的紋絲不亂的頭發(fā)上勺阐,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音矛双,去河邊找鬼渊抽。 笑死,一個胖子當(dāng)著我的面吹牛议忽,可吹牛的內(nèi)容都是我干的懒闷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼栈幸,長吁一口氣:“原來是場噩夢啊……” “哼愤估!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起速址,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤玩焰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后芍锚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昔园,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蔓榄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了默刚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甥郑。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖羡棵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嗅钻,我是刑警寧澤皂冰,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站养篓,受9級特大地震影響秃流,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柳弄,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一舶胀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碧注,春花似錦嚣伐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逝变,卻和暖如春基茵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背壳影。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工拱层, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宴咧。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓根灯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掺栅。 傳聞我的和親對象是個殘疾皇子箱吕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

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