在學習gevent以前,你確定要知道你學的這個東西是什麼。git
gevent is a coroutine-based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libev event loop.程序員
翻譯:gevent是一個基於協程的Python網絡庫。咱們先理解這句,也是此次學習的重點——協程。github
與子例程同樣,協程也是一種程序組件。相對子例程而言,協程更爲通常和靈活,但在實踐中使用沒有子例程那樣普遍。子例程的起始處是唯一的入口點,一旦退出即完成了子例程的執行,子例程的一個實例只會返回一次;協程能夠經過yield來調用其它協程。經過yield方式轉移執行權的協程之間不是調用者與被調用者的關係,而是彼此對稱、平等的。協程容許多個入口點,能夠在指定位置掛起和恢復執行。安全
沒看懂?不要緊,我也沒看懂,不過算是有點線索:子例程。網絡
過程有兩種,一種叫子例程(Subroutine),一般叫Sub;另外一種叫函數(Function)。底層實現機制是同樣的,區別在於,Sub只執行操做,沒有返回值;Function不但執行操做,而且有返回值。用過VB的應該會比較清楚這點。(原諒我用了百度百科)說到底子例程就是過程,咱們通常叫它函數。多線程
說到函數,我就想吐槽了,不明白爲何要叫函數。不少時候咱們寫一個函數是爲了封裝、模塊化某個功能,它是一個功能、或者說是一個過程。由於它包含的是相似於流程圖那樣的具體邏輯,先怎樣作,而後怎樣作;若是遇到A狀況則怎樣,若是遇到B狀況又怎樣。我的以爲仍是叫過程比較好,叫作函數就讓人很糾結了,難道由於迴歸到底層仍是計算問題,出於數學的角度把它稱爲函數?這個略坑啊!爲了符合你們的口味,我仍是稱之爲函數好了(其實我也習慣叫函數了%>_<%)。併發
講到函數,咱們就往底層深刻一點,看看下面的代碼:dom
def a(): print "a start" b() print "a end" def b(): print "b start" c() print "b end" def c(): print "c start" print "c end" if __name__ == "__main__": a() a start b start c start c end b end a end
對於這樣的結果你們確定不會意外的。每當函數被調用,就會在棧中開闢一個棧空間,調用結束後再回收該空間。ide
假設一個這樣的場景:有個講臺,每一個人均可以上去發表言論,可是每次講臺只能站一我的。如今a在上面演講,當他說到「你們好!」的時候,b有個緊急通知要告訴你們,因此a就先下來讓b講完通知,而後a再上講臺繼續演講。若是用函數的思想模擬這個問題,堆棧示意圖是這樣的:模塊化
你們會不會發現問題,就是b通知完a繼續演講都要從新開始。由於函數在從新調用的時候,它的局部變量是會被重置的,對於以前他說的那句「你們好」,他是不會記得的(可能a的記性很差)。那有沒有什麼辦法能夠不讓他重複,而是在打斷以後繼續呢?很簡單,在他走下講臺以前記住當前說過的話。表如今函數中就是在退出以前,保存該函數的局部變量,方便在從新進入該函數的時候,可以從以前的局部變量開始繼續執行。
若是你有一段代碼生產數據,另一段代碼消費數據,哪一個應該是調用者,哪一個應該是被調用者?
例如:生產者 —— 消費者問題,先拋開進程、線程等實現方法。假設有兩個函數producer和consumer,當緩衝區滿了,producer調用consumer,當緩衝區空了,consumer調用producer,可是這樣的函數互相調用會出什麼問題?
def producer(): print "生產一個" consumer() def consumer(): print "消費一個" producer()
producer生產一個,緩衝區滿了,consumer消費一個,緩衝區空了,producer生產一個,如此循環。會看到下面這樣的圖:
看起來好像不錯,感受兩個函數協調運行的很好,很好的解決了生產者——消費者問題。若是真有這麼好也就不會有協程的存在了,仔細分析會有兩個問題:
首先,上面的僞代碼示例是一個無限的函數嵌套調用,沒有函數返回來釋放棧,棧的空間不斷的在增加,直到溢出,程序崩潰。而後,看起來兩個函數協調有序,事實上操做的都不是同一個實例對象,不知道下面的圖可否看懂。
那什麼東西有這樣的能力呢?咱們很快就能夠想到進程、線程,可是你真的想使用進程、線程如此重量級的東西在這麼簡單的程序上嗎?野蠻的搶佔式機制和笨重的上下文切換!
還有一種程序組件,那就是協程。它能保留上一次調用時的狀態,每次從新進入該過程的時候,就至關於回到上一次離開時所處邏輯流的位置。協程的起始處是第一個入口點,在協程裏,返回點以後是接下來的入口點。協程的生命期徹底由他們的使用的須要決定。每一個協程在用yield命令向另外一個協程交出控制時都儘量作了更多的工做,放棄控制使得另外一個協程從這個協程中止的地方開始,接下來的每次協程被調用時,都是從協程返回(或yield)的位置接着執行。
從上面這些你就能夠知道其實協程是模擬了多線程(或多進程)的操做,多線程在切換的時候都會有一個上下文切換,在退出的時候將現場保存起來,等到下一次進入的時候從保存的現場開始,繼續執行。
看下協程是怎樣實現的:
import random from time import sleep from greenlet import greenlet from Queue import Queue queue = Queue(1) @greenlet def producer(): chars = ['a', 'b', 'c', 'd', 'e'] global queue while True: char = random.choice(chars) queue.put(char) print "Produced: ", char sleep(1) consumer.switch() @greenlet def consumer(): global queue while True: char = queue.get() print "Consumed: ", char sleep(1) producer.switch() if __name__ == "__main__": producer.run() consumer.run()
咱們一直都在大談協程是什麼樣一個東西,卻從沒有提起協程用來幹嗎,這個其實你們分析一下就可以知道。從上面的生產者——消費者問題應該能看出,它分別有兩個任務,假設交給兩我的去執行,但每次只能容許一我的行動。當緩衝區滿的時候,生產者是出於等待狀態的,這個時候能夠將執行任務的權利轉交給消費者,當緩衝區空得時候,消費者是出於等待狀態的,這個時候能夠將執行任務的權利轉交給生產者,是否是很容易聯想到多任務切換?而後想到線程?最後想到高併發?
但同窗們又會問,既然有了線程爲何還要協程呢?由於線程是系統級別的,在作切換的時候消耗是特別大的,具體爲何這麼大等我研究好了再告訴你;同時線程的切換是由CPU決定的,可能你恰好執行到一個地方的時候就要被迫終止,這個時候你須要用各類措施來保證你的數據不出錯,因此線程對於數據安全的操做是比較複雜的。而協程是用戶級別的切換,且切換是由本身控制,不受外力終止。
協程其實模擬了人類活動的一種過程。例如:你準備先寫文檔,而後修復bug。這時候接到電話說這個bug很嚴重,必須當即修復(能夠看做CPU通知)。因而你暫停寫文檔,開始去填坑,終於你把坑填完了,你回來寫文檔,這個時候你確定是接着以前寫的文檔繼續,難道你要把以前寫的給刪了,從新寫?這就是協程。那若是是子例程呢?那你就必須從新寫了,由於退出以後,棧幀就會被彈出銷燬,再次調用就是開闢新的棧空間了。
總結:協程就是用戶態下的線程,是人們在有了進程、線程以後仍以爲效率不夠,而追求的又一種高併發解決方案。爲何說是用戶態,是由於操做系統並不知道它的存在,它是由程序員本身控制、互相協做的讓出控制權而不是像進程、線程那樣由操做系統調度決定是否讓出控制權。