Python Gevent

參考資料

Python腳本的執(zhí)行效率一直來說并不是很高灭袁,特別是Python下的多線程機制渠概,長久以來一直被人們詬病税肪。很多人都在思考如何讓Python執(zhí)行的更快一些剖煌,其中典型的方式包括:

  • 將復雜的代碼轉(zhuǎn)由C語言完成
  • 多進程并發(fā)執(zhí)行
  • 多線程完成IO操作

然后裆装,人們討論的更多的則是Gevent的協(xié)程機制晕粪。在理解Gevent之前颠蕴,我們需要弄明白幾個基本的概念:進程(Process)泡挺、線程(Thread)辈讶、協(xié)程(Coroutine)、計算機IO方式等娄猫。

協(xié)程

進程和線程都是操作系統(tǒng)中的模型贱除,操作系統(tǒng)通過進程和線程這兩種模型來執(zhí)行程序。進程是操作系統(tǒng)分配資源(如CPU媳溺、內(nèi)存等)和調(diào)度的基本單位月幌,可以將其看作是包含系統(tǒng)分配的資源和執(zhí)行流兩部分,通過進程模型悬蔽,操作系統(tǒng)可以靈活地管理程序的執(zhí)行扯躺。線程是執(zhí)行流,一般而言一個進程只包含一個執(zhí)行流蝎困,也就是說一個進程只包含一個線程录语,通過線程模型,一個進程可以擁有多個執(zhí)行流难衰,進而提供程序的處理能力钦无。

協(xié)程coroutine其實是corporate routine的縮寫,直譯為協(xié)同的例程盖袭,簡稱為協(xié)程失暂。在Linux中線程是輕量級的進程,因此也將協(xié)程coroutine稱為輕量級的線程鳄虱,又稱為微線程弟塞。協(xié)程簡單的理解就是程序的執(zhí)行流,在暫停和再次執(zhí)行的過程中可以記錄當前的狀態(tài)拙已,在暫停后需要再次執(zhí)行時可以從暫停前的狀態(tài)繼續(xù)執(zhí)行决记。協(xié)程暫停執(zhí)行時,可以調(diào)度到另一個協(xié)程執(zhí)行倍踪,這兩個協(xié)程之間的關(guān)系是對等的系宫。

協(xié)程和生成器generator的概念很像,生成器也可以保存當前執(zhí)行狀態(tài)并再次運行時恢復之前的狀態(tài)建车,不過區(qū)別在于協(xié)程暫停時可以調(diào)度到另一個協(xié)程執(zhí)行扩借,而生成器暫停時會由它的調(diào)用者繼續(xù)執(zhí)行。

協(xié)程的調(diào)度由使用者所決定缤至,而不像進程和線程那樣由操作系統(tǒng)進行調(diào)度潮罪,Gevent中的協(xié)程在暫停時會執(zhí)行一個稱為Hub的協(xié)程,由Hub選擇處于等待執(zhí)行狀態(tài)的協(xié)程繼續(xù)執(zhí)行流。

線程是搶占式的調(diào)度嫉到,多個線程并行執(zhí)行時搶占共同的系統(tǒng)資源沃暗,而協(xié)程則是協(xié)同式的調(diào)度。其實Greenlet并非一種真正的并發(fā)機制何恶,而是在同一線程內(nèi)的不同函數(shù)的執(zhí)行代碼塊之間進行切換孽锥,實施“你運行一會兒,我運行一會兒”细层,在進行切換時必須制定何時切換以及切換到哪兒忱叭。

進程與協(xié)程

進程與協(xié)程有什么異同點呢?

進程與協(xié)程都可以看作是一種執(zhí)行流今艺,執(zhí)行流可以掛起,后續(xù)可以在掛起的位置恢復執(zhí)行爵卒。

例如:在Linux的Shell中執(zhí)行Hello程序

開始時Shell進程在運行并等待命令行的輸入虚缎,當執(zhí)行Hello程序時,Shell通過系統(tǒng)調(diào)用來執(zhí)行請求钓株,此時系統(tǒng)調(diào)用會將控制權(quán)傳遞給操作系統(tǒng)实牡,操作系統(tǒng)保存Shell進程的上下文并創(chuàng)建Hello進程以及上下文并將控制權(quán)交給Hello進程。當Hello進程終止后操作系統(tǒng)恢復Shell進程的上下文轴合,并將控制權(quán)傳回給Shell進程创坞,Shell進程繼續(xù)等待下一個命令的輸入。

進程與協(xié)程相同點

當掛起一個執(zhí)行流時受葛,此時需要保存兩樣東西题涨,其一是棧,其實在切換前局部變量以及函數(shù)的調(diào)用都需要保存否則將無法恢復总滩,其二是寄存器狀態(tài)纲堵,寄存器狀態(tài)用于當執(zhí)行流恢復后需要執(zhí)行什么。寄存器和棧的結(jié)合可以理解為上下文闰渔,上下文切換的理解是CPU看上去像是在并行執(zhí)行多個進程席函,這其實是通過CPU在進程間切換來實現(xiàn)的,操作系統(tǒng)實現(xiàn)這種交錯執(zhí)行的機制稱為上下文切換冈涧。操作系統(tǒng)保存跟蹤進程運行所需的所有狀態(tài)信息茂附,這種狀態(tài)就是上下文。在任意時刻操作系統(tǒng)只能執(zhí)行一個進程代碼督弓,當操作系統(tǒng)決定將控制權(quán)從當前進程轉(zhuǎn)移到某個進程時营曼,就會進行上下文切換,也就是保存當前進程的上下文咽筋,并恢復新進程的上下文溶推。然后將控制權(quán)傳遞給新進程,新進程從從它上次停止的地方開始。

進程與協(xié)程的不同點在于

  • 執(zhí)行流的調(diào)用者不同蒜危,進程是內(nèi)核調(diào)度虱痕,而協(xié)程是用戶態(tài)調(diào)度,也就是說進程的上下文是在內(nèi)核態(tài)中保存并恢復的辐赞,而協(xié)程是在用戶態(tài)保存恢復的部翘,很顯然用戶態(tài)的代價要跟低。
  • 進程會被強占响委,而協(xié)程不會新思。也就是說協(xié)程如果不主動讓出CPU,那么其他協(xié)程就沒有執(zhí)行的機會赘风。
  • 對內(nèi)存的占用不同夹囚,協(xié)程只需要4KB的棧空間就足夠了邀窃,而進程占用的內(nèi)存要大的多荸哟。
  • 從操作系統(tǒng)角度講瞬捕,多協(xié)程的程序是單進程單協(xié)程的。

線程與協(xié)程

線程與協(xié)程有什么異同點呢肪虎?

協(xié)程也被稱為微線程,線程之間上下文切換的成本相對于協(xié)程而言是比較高的扇救,尤其是開啟的線程較多的時候。而協(xié)程的切換成本則比較低仅讽。另外,同樣線程的切換更多的是依靠操作系統(tǒng)來控制钾挟,而協(xié)程的執(zhí)行則是由用戶自己來控制洁灵。

計算機IO方式

根據(jù)計算機組成原理,計算機中IO的控制方式包括程序查詢方式掺出、程序中斷方式徽千、DMA方式、通道方式汤锨。目前計算機采用DMA和通道方式進行IO控制双抽,這樣在進行IO操作時,CPU可以盡量不參與到這個過程中而去執(zhí)行其它的操作闲礼。由于IO操作一般都比較耗時牍汹,采用DMA和通道方式可以將CPU從IO過程中解放出來铐维,從而提高系統(tǒng)的效率。

在程序運行過程中一把會遇到兩種類型的IO慎菲,即當前機器的磁盤IO和網(wǎng)絡IO嫁蛇,這兩種IO操作一般會阻塞程序的執(zhí)行,浪費CPU時間露该,因為此時程序分配到了時間片睬棚,在該時間片內(nèi)程序是獨占CPU資源,由于IO被阻塞CPU沒有被其他程序享有進而被浪費解幼。因此在編寫高性能程序時抑党,IO是需要重點關(guān)注的。

目前可以通過如下途徑解決因IO帶來的效率問題:

  • 減少IO次數(shù)撵摆,也就是優(yōu)化程序的結(jié)構(gòu)底靠,將需要讀寫的數(shù)據(jù)匯集在一起進行一次性讀寫。
  • 提高硬件的IO速度特铝,比如使用SSD磁盤苛骨。
  • IO時不阻塞當前執(zhí)行流,由DMA控制器或通道負責IO操作苟呐,CPU繼續(xù)執(zhí)行程序的其他部分或執(zhí)行其他程序膛檀。

大多涉及到IO的高性能庫一般都是通過第三種途徑解決IO的性能瓶頸修噪,例如Tornado的異步操作,而Gevent正是基于Greenlet的協(xié)程巡李。Gevent實現(xiàn)了Python標準庫中一些阻塞庫的非阻塞版本笆呆,如socket赠幕、os榕堰、select等逆屡,可以使用這些非阻塞的庫替代Python中阻塞的庫魏蔗。

網(wǎng)絡IO模型

  • 阻塞式單線程

最基本的IO模型莺治,只有在處理完畢一個請求后才能處理下一個請求,缺點是效能差床佳,如果有請求阻塞住夕土,會讓服務無法繼續(xù)接受請求怨绣,但這種模型編寫代碼相對比較簡單篮撑,在應對訪問量不大的情況下非常適用赢笨。

  • 阻塞式多線程

針對于單線程接受請求數(shù)量有限的缺點茧妒,一個很自然的想法是給每個請求開一個線程去處理桐筏,這樣做的好處是能夠接受更多的請求梅忌。缺點是當線程產(chǎn)生到一定數(shù)量之后牧氮,進程之間需要大量上下文切換的操作踱葛,此時會占用CPU大量的時間剖毯,不過這樣處理的話編寫代碼的難度稍高于單進程的情況逊谋。

  • 非阻塞式事件驅(qū)動

為了解決多線程的問題胶滋,有一種做法是利用循環(huán)來檢查是否有網(wǎng)絡IO事件的發(fā)生,以便決定如何來進行處理俭令,比如Reactor設(shè)計模式抄腔。這種做法的好處是進一步降低了CPU的資源消耗赫蛇,缺點是這樣做會讓程序難以編寫悟耘,因為請求接受后的處理過程由Reactor來決定暂幼,使得程序的執(zhí)行流程難以把握旺嬉。當接收到一個請求后如果涉及到阻塞的操作鹰服,這個請求的處理過程會停下來去接受另一個請求,程序執(zhí)行的流程不會像線性程序那樣直觀亲善,比如Twisted框架就是采用此種模型的典型案例蛹头。

  • 非阻塞式協(xié)程

非阻塞式協(xié)程是為了解決事件驅(qū)動模型執(zhí)行流程不直觀的問題渣蜗,在本質(zhì)上也是事件驅(qū)動的耕拷,但加入了協(xié)程的概念骚烧。

Gevent

Gevent是一種基于協(xié)程的Python網(wǎng)絡庫,使用Greenlet提供并封裝了libevent事件循環(huán)的高層同步API既峡,使開發(fā)者在不改變編程習慣的同時运敢,以同步的方式編寫異步IO代碼传惠。簡單來說档痪,Gevent是基于libev和Greenlet的一個異步的Python框架腐螟。

libev是一個高性能的事件循環(huán)event loop實現(xiàn)衬廷。事件循環(huán)(IO多路復用)是解決阻塞問題實現(xiàn)并發(fā)的一種方式汽绢。事件循環(huán)event loop會捕獲并處理IO事件的變化宁昭,當遇到阻塞時就會跳出积仗,當阻塞結(jié)束時則會繼續(xù)寂曹。這一點依賴于操作系統(tǒng)底層的select函數(shù)及其升級版pollepoll隆圆。而Greenlet則是一個Python的協(xié)程管理和切換的模塊渺氧,通過Greenlet可以顯式地在不同的任務之間進行切換侣背。

Libev

Gevent的基本原理來自于libevent&libev,熟悉C語言的同學應該對這個lib不陌生业筏。本質(zhì)上libevent或者說libev都是一種事件驅(qū)動模型鸟赫。這種模型對于提高CPU的運行效率台谢,增強用戶的并發(fā)訪問非常有效朋沮。但因為其本身是一種事件機制缀壤,所以編寫起來有些繞筋夏,并不是很直觀图呢。因此為了修正這個問題蛤织,有人引入了用戶態(tài)上下文切換機制指蚜,也就是說,如果代碼中引入了帶IO阻塞的代碼時,lib本身會自動完成上下文的切換柱宦,全程用戶都是沒有察覺的,這又是Gevent的由來播瞳。

Libev是高性能事件循環(huán)模型的網(wǎng)絡庫忧侧,包含大量新特性,是繼libevent之后的一套全新的網(wǎng)絡庫松逊。libev追求的目標是速度更快经宏、bug更少、特性更多徊都、體積更小暇矫。和libevent類似杀餐,可以作為其替代者朱巨,提供更高的性能且無需復雜的配置琼讽。

libev機制提供了對指定文件描述符發(fā)生時調(diào)用回調(diào)函數(shù)的機制钻蹬,libev是一個事件循環(huán)器问欠,向libev注冊感興趣的事件顺献,比如Socket可讀事件注整,libev會對所注冊的事件的源進行管理肿轨,并在事件發(fā)生時出發(fā)相應的程序椒袍。

Yield

Python對協(xié)程的支持是非常有限的曙蒸,使用生成器generator中的yield可以一定程序上實現(xiàn)協(xié)程纽窟。比如傳統(tǒng)的生產(chǎn)者-消費者模型臂港,即一個線程寫消息一個線程讀消息,通過鎖機制控制隊列和等待佑力,但一不小心就可能出現(xiàn)死鎖打颤。如果改用協(xié)程,生產(chǎn)者生產(chǎn)消息后直接通過yield跳轉(zhuǎn)到消費者并開始執(zhí)行响驴,等消費者執(zhí)行完畢后再切換回生產(chǎn)者繼續(xù)生產(chǎn)秽誊,這樣做效率極高。

$ vim test.py
#! /usr/bin/env python3
# -*- coding:utf-8 -*-

import time

def consumer():
    r = ""
    while True:
        n = yield r
        if not n:
            return
        print("consumer %s"%n)
        r = "200 OK"

def producer(c):
    c.__next__()
    n = 0
    while n < 3:
        n = n + 1
        print("producer %s"%n)
        r = c.send(n)
        print("producer return %s\n"%r)
    c.close()

if __name__ == "__main__":
    c = consumer()
    producer(c)
$ python3 test.py
producer 1
consumer 1
producer return 200 OK

producer 2
consumer 2
producer return 200 OK

producer 3
consumer 3
producer return 200 OK

代碼分析:首先調(diào)用c.__next__()啟動生成器日熬,一旦生產(chǎn)出東西棍厌,則通過c.send(n)切換到消費者consumer來執(zhí)行,消費者consumer通過yield獲取到消息后處理竖席,然后通過yield將結(jié)果傳回耘纱。生產(chǎn)者producer獲取到消費者consumer處理的結(jié)果后繼續(xù)生產(chǎn)下一條消息。整個過程無鎖毕荐,由一個線程執(zhí)行束析,生產(chǎn)者和消費者協(xié)作完成任務,所以稱之為協(xié)程憎亚。

Python通過yield提供了對協(xié)程的基本支持员寇,但并不完全蝶锋。而第三方的Gevent為Python提供了比較完善的協(xié)程支持别威,Gevent是第三方庫,可通過Greenlet實現(xiàn)協(xié)程。另外臀栈,Python中由于GIL的存在導致多線程一直不是很好用,相比之下,協(xié)程的優(yōu)勢就更加突出了。

Greenlet

Greenlet是指使用一個任務調(diào)度器和一些生成器或協(xié)程實現(xiàn)協(xié)作式用戶空間多線程的一種偽并發(fā)機制甩鳄,也就是所謂的微線程。Greenlet機制的主要思想是生成器函數(shù)或協(xié)程函數(shù)中的yield語句掛起函數(shù)的執(zhí)行,直到稍后使用next()send()操作進行恢復為止盆昙×锻牛可以使用一個調(diào)度器循環(huán)在一組生成器函數(shù)在將協(xié)作多個任務。

既然Gevent使用的是Greenlet,因此需要理解Greenlet的工作原理:每個協(xié)程都有一個parent,最頂層的協(xié)程是man thread或者是當前線程鲫咽,每個協(xié)程遇到IO時會見控制權(quán)交給最頂層的協(xié)程括丁,它會檢測到哪個協(xié)程的IO Event已經(jīng)完成并將控制權(quán)交給它。

Greenlet

Greenlet的基本思路是:當一個Greenlet遇到IO操作時己单,比如訪問網(wǎng)絡時會自動切換到其它的Greenlet廷痘,等到IO操作完成兄猩,再在適當?shù)臅r候切換回來繼續(xù)執(zhí)行。由于IO操作非常耗時,經(jīng)常會使程序處于等待狀態(tài)虐唠,有了Gevent自動切換協(xié)程,就保證總有Greenlet在運行撤蟆,而不是等待IO值依。由于切換是在IO操作時自動完成,所以Gevent需要修改Python自帶的標準庫碍沐,這一過程在啟動時通過monkey patch猴子補丁來完成缔赠。

Swich

一個Greenlet是一個很小的獨立微線程置森,可以把它想象成一個堆棧幀崭捍,棧底是初始調(diào)用,棧頂是當前Greenlet的暫停位置宴猾,使用Greenlet創(chuàng)建一堆這樣的堆棧仇哆,然后在它們之間跳轉(zhuǎn)執(zhí)行延欠。跳轉(zhuǎn)并不是絕對的涧窒,因為一個Greenlet必須選擇跳轉(zhuǎn)到選擇好的另一個Greenlet戴已,這會讓前一個掛起,而后一個恢復羽资,兩個Greenlet之間的跳轉(zhuǎn)又被稱之為切換switch。當創(chuàng)建一個Greenlet時它會得到一個初始化過的空堆棧遵班,當?shù)谝淮吻袚Q到它時會啟動指定的函數(shù)屠升,然后切換跳出Greenlet,當最終棧底函數(shù)結(jié)束時狭郑,Greenlet的堆棧又會變成空的腹暖,而Greenlet也就死掉了。當然翰萨,Greenlet也會因為一個未捕獲的異常而死掉脏答。

Monkey-patching

Monkey-patching猴子補丁這個叫法源自于Zope框架,大家在修正Zope的Bug時經(jīng)常會在程序后追加更新部分亩鬼,這些被稱作“雜牌軍補吨掣妗(guerillapatch)”,后來guerilla逐漸寫成了gorllia(猩猩)辛孵,再后來就寫成了monkey(猴子)丛肮,所以猴子補丁的叫法就這么莫名其妙的得來了赡磅。之后在動態(tài)語言中魄缚,不改變源代碼而對功能進行追加和變更就統(tǒng)稱為“猴子補丁”焚廊。所以猴子補丁并不是Python中專有的冶匹,猴子補丁充分利用了動態(tài)語言的靈活性咆瘟,可以對現(xiàn)有語言API進行追加、替換袒餐、修改飞蛹,甚至性能優(yōu)化等。使用猴子補丁的方式Gevent能夠修改標準庫中大部分的阻塞式系統(tǒng)調(diào)用灸眼,包括socketssl焰宣、threadingselect等模塊匕积,使其變?yōu)閰f(xié)作式運行盈罐。

Monkey-patching猴子補丁是將標準庫中大部分的阻塞式調(diào)用替換成非阻塞的方式榜跌,包括socketssl盅粪、threadingselect湾揽、httplib等。通過monkey.path_xxx()函數(shù)來打補丁库物,根據(jù)Gevent文檔中的建議,應當將猴子補丁的代碼盡可能早的被調(diào)用戚揭,這樣可以避免一些奇怪的異常。

使用Gevent的性能要比傳統(tǒng)的線程高民晒,但不得不說的一個坑是如果使用Monkey-patching猴子補丁,Gevent將直接修改標準庫中大部分的阻塞式調(diào)用潜必,包括socket靴姿、sslthreading佛吓、select等模塊,而變?yōu)閰f(xié)作式運行垂攘。但無法保證在復雜的生產(chǎn)環(huán)境中那些地方使用標準庫因補丁而出現(xiàn)的奇怪問題。另外是第三方庫的支持晒他,需要確保項目中使用到的其他網(wǎng)絡庫也必須使用純Python或明確支持Gevent吱型。

Gevent應該在什么場景中使用呢陨仅?

Gevent的優(yōu)勢在于可以通過同步的邏輯實現(xiàn)并發(fā)操作津滞,大大降低編寫并行或并發(fā)程序的難度灼伤。另外触徐,在一個進程中使用Gevent可以有效地避免對臨界資源的互斥訪問饺蔑。如果程序中涉及到較多的IO锌介,可以使用Gevent替代多線程來提高程序的效率,但是由于Gevent中的協(xié)程的調(diào)度是由使用者而非操作系統(tǒng)決定的,Gevent主要解決的問題是IO問題隆敢,通過提高IO-bound類型的程序的效率,另外由于是在一個進程中實現(xiàn)協(xié)程崔慧,而操作性i同是以進程為單位分配處理資源的(一個進程分配一個處理機)。因此惶室,Gevent并不適合對任務延遲有要求的場景温自,比如交互式程序中皇钞。也不適用于CPU-bound類型的任務和需要使用多處理機的場景(通過運行多個進程悼泌,每個進程內(nèi)實現(xiàn)協(xié)程來解決這個問題夹界。)馆里。

安裝使用

Ubuntu系統(tǒng)下可通過apt-get安裝gevent

$ sudo apt-get install python-gevent

如果使用的Python3的版本,安裝如下:

$ sudo apt-get install python3-gevent

也可以直接使用Python的包管理工具pip命令進行安裝鸠踪,不過需要注意版本與權(quán)限。

$ pip install gevent

入門案例:使用Gevent控制程序執(zhí)行順序

$ vim test.py
#! /usr/bin/env python3
# -*- coding:utf-8 -*-

import gevent
from gevent import monkey

monkey.patch_socket()

def fn(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
# 創(chuàng)建協(xié)程對象
greenlet1 = gevent.spawn(fn, 3)
greenlet2 = gevent.spawn(fn, 3)
# 等待greenlet1執(zhí)行結(jié)束
greenlet1.join()
greenlet2.join()
# 獲取fn的返回值
# print(greenlet1.value)
$ python3 test.py
<Greenlet at 0x7ff0b1f6e508: fn(3)> 0
<Greenlet at 0x7ff0b1f6e508: fn(3)> 1
<Greenlet at 0x7ff0b1f6e508: fn(3)> 2
<Greenlet at 0x7ff0b1f6ea60: fn(3)> 0
<Greenlet at 0x7ff0b1f6ea60: fn(3)> 1
<Greenlet at 0x7ff0b1f6ea60: fn(3)> 2

根據(jù)執(zhí)行結(jié)果可知:greenlet是依次運行而不是交替運行的复斥,如果要讓greenlet交替運行則需要通過gevent.sleep()交出控制權(quán)。
green.spawn會啟動所有協(xié)程目锭,協(xié)程都是運行在同一個線程之中的评汰,所以協(xié)程不能夠跨線程同步數(shù)據(jù)侣集。

$ vim test.py
#! /usr/bin/env python3
# -*- coding:utf-8 -*-

import gevent
from gevent import monkey

monkey.patch_socket()

def fn(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0)

greenlet1 = gevent.spawn(fn, 3)
greenlet2 = gevent.spawn(fn, 3)
# 合并兩步操作
gevent.joinall([greenlet1, greenlet2])
$ python3 test.py
<Greenlet at 0x7f53b09a1508: fn(3)> 0
<Greenlet at 0x7f53b09a1a60: fn(3)> 0
<Greenlet at 0x7f53b09a1508: fn(3)> 1
<Greenlet at 0x7f53b09a1a60: fn(3)> 1
<Greenlet at 0x7f53b09a1508: fn(3)> 2
<Greenlet at 0x7f53b09a1a60: fn(3)> 2

程序的重要部分是將任務函數(shù)封裝到gevent.spawn中键俱,初始化的Greenlet列表存放在數(shù)組線程中世分,此數(shù)組會被傳給gevent.joinall函數(shù),gevent.joinall函數(shù)會阻塞當前流程缀辩,并執(zhí)行所有給定的Greenlet,執(zhí)行流只會在所有的Greenlet執(zhí)行完畢后才會繼續(xù)向下執(zhí)行臀玄。

在Gevent中gevent.sleep()模擬的是Gevent可以識別的IO阻塞瓢阴,若使用time.sleep()或其他阻塞健无,Gevent是不能夠直接識別的荣恐,需要使用猴子補丁,注意猴子補丁必須放在被打補丁的前面,如time叠穆、socket模塊之前少漆。

#! /usr/bin/env python3
# -*- coding:utf-8 -*-

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

def fn(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(1)

greenlet1 = gevent.spawn(fn, 3)
greenlet2 = gevent.spawn(fn, 3)
gevent.joinall([greenlet1, greenlet2])

案例:Greenlet模塊內(nèi)部使用了協(xié)程的概念硼被,在單線程內(nèi)需要手動調(diào)用switch函數(shù)切換協(xié)程示损。

$ vim test.py
#! /usr/bin/env python3
# -*- coding:utf-8 -*-

from greenlet import greenlet

def eat(name):
    print("%s eat 1"%name)
    g2.switch("egon")
    print("%s eat 2"%name)
    g2.switch()

def play(name):
    print("%s play 1"%name)
    g1.switch()
    print("%s play 2"%name)

g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch("egon")
$ python3 test.py
egon eat 1
egon play 1
egon eat 2
egon play 2

根據(jù)上述代碼可以發(fā)現(xiàn)嚷硫,協(xié)程一旦遇到IO操作就會自動切換到其它協(xié)程检访,使用yield是無法實現(xiàn)的。

例如:

$ vim test.py
#! /usr/bin/env python3
# -*- coding:utf-8 -*-

import gevent
from gevent import socket

urls = [
    "www.baidu.com",
    "www.python.org",
    "www.example.com"
]

jobs = [
    gevent.spawn(socket.gethostbyname, url) for url in urls        
]

gevent.joinall(jobs, timeout=2)

result = [
    job.value for job in jobs        
]

print(result)
$ python3 test.py
['112.80.248.75', '151.101.108.223', '93.184.216.34']

使用gevent.spawn函數(shù)spawn引發(fā)一些任務jobs脆贵,再通過gevent.joinall將所有任務jobs加入到為協(xié)程執(zhí)行隊列中等待其完成,同時設(shè)置超時時間為2秒起暮。執(zhí)行后的結(jié)果通過檢查gevent.greenlet.value值來收集。

gevent.socket.gethostbyname函數(shù)與標準的socket.gethostbyname有著相同的接口鞋怀,但不會阻塞整個解釋器双泪,因此會使其他Greenlet跟隨著無阻塞的請求而執(zhí)行密似。

猴子補丁

from gevent import monkey

patch_all

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
)

Gevent的Monkey可以為socket焙矛、dns、time残腌、select、thread抛猫、os蟆盹、ssl闺金、httplib逾滥、subprocess、sys寨昙、aggressive、Event掀亩、builtins、signal模塊打上的補丁槽棍,打上補丁后他們就是非阻塞的了捉蚤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缆巧,隨后出現(xiàn)的幾起案子怎爵,更是在濱河造成了極大的恐慌盅蝗,老刑警劉巖鳖链,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墩莫,死亡現(xiàn)場離奇詭異,居然都是意外死亡狂秦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門侧啼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人堪簿,你說我怎么就攤上這事⊥指” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵湿滓,是天一觀的道長。 經(jīng)常有香客問我叽奥,道長,這世上最難降的妖魔是什么朝氓? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮恋日,結(jié)果婚禮上膀篮,老公的妹妹穿的比我還像新娘岂膳。我一直安慰自己磅网,他們只是感情好谈截,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著毙死,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喻鳄。 梳的紋絲不亂的頭發(fā)上扼倘,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音再菊,去河邊找鬼颜曾。 笑死纠拔,一個胖子當著我的面吹牛泛豪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诡曙,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼价卤!你這毒婦竟也來了匹耕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤荠雕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后炸卑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嘱蛋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年五续,在試婚紗的時候發(fā)現(xiàn)自己被綠了洒敏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疙驾。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖函荣,靈堂內(nèi)的尸體忽然破棺而出显押,到底是詐尸還是另有隱情傻挂,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布金拒,位于F島的核電站,受9級特大地震影響绪抛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睦疫,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一蛤育、第九天 我趴在偏房一處隱蔽的房頂上張望宛官。 院中可真熱鬧瓦糕,春花似錦、人聲如沸咕娄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至圣贸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吁峻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工矮慕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痴鳄。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓肠缔,卻偏偏與公主長得像哼转,于是被迫代替她去往敵國和親明未。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345