1、協程的理解
協程,又稱微線程,纖程。英文名Coroutine,是一種用戶態的輕量級線程。
注意:
1. python的線程屬於內核級別的,即由操作系統控制調度(如單線程一旦遇到io就被迫交出cpu執行權限,切換其他線程運行)
2. 單線程內開啟協程,一旦遇到io,從應用程序級別(而非操作系統)控制切換
協程優點:
1. 協程的切換開銷更小,屬於程序級別的切換,操作系統完全感知不到,因而更加輕量級
2. 單線程內就可以實現並發的效果,最大限度地利用cpu
協程缺點:
1.協程的本質是單線程下,無法利用多核,可以是一個程序開啟多個進程,每個進程內開啟多個線程,每個線程內開啟協程
2.協程指的是單個線程,因而一旦協程出現阻塞,將會阻塞整個線程
協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方(線程調度時候寄存器上下文及棧等保存在內存中),在切回來的時候,恢復先前保存的寄存器上下文和棧。因此:協程能保留上一次調用時的狀態(即所有局部狀態的一個特定組合),每次過程重入時,就相當於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。
子程序,或者稱為函數,在所有語言中都是層級調用,比如A調用B,B在執行過程中又調用了C,C執行完畢返回,B執行完畢返回,最后是A執行完畢。
所以子程序調用是通過棧實現的,一個線程就是執行一個子程序。子程序調用總是一個入口,一次返回,調用順序是明確的。而協程的調用和子程序不同。
協程看上去也是子程序,但執行過程中,在子程序內部可中斷,然后轉而執行別的子程序,在適當的時候再返回來接着執行。
注意,在一個子程序中中斷,去執行其他子程序,不是函數調用,有點類似CPU的中斷。比如子程序A、B:
def A(): print '1' print '2' print '3' def B(): print 'x' print 'y' print 'z'
假設由協程執行,在執行A的過程中,可以隨時中斷,去執行B,B也可能在執行過程中中斷再去執行A,結果可能是:
1 2 x y 3 z
但是在A中是沒有調用B的,所以協程的調用比函數調用理解起來要難一些。
看起來A、B的執行有點像多線程,但協程的特點在於是一個線程執行。
2、yield的運用
從句法上看,協程與生成器類似,都是定義體中包含 yield 關鍵字的函數。可是,在協程中, yield 通常出現在表達式的右邊(例如, datum = yield),可以產出值,也可以不產出 —— 如果 yield 關鍵字后面沒有表達式,那么生成器產出 None。
協程可能會從調用方接收數據,不過調用方把數據提供給協程使用的是 .send(datum) 方法,而不是next(…) 函數。
==yield 關鍵字甚至還可以不接收或傳出數據。不管數據如何流動, yield 都是一種流程控制工具,使用它可以實現協作式多任務:協程可以把控制器讓步給中心調度程序,從而激活其他的協程==。
import random def create_generator_function(n): i = 0 while i < n: b = yield i print("It's {0}".format(b)) i = i + 1 obj = create_generator_function(5) obj1 = obj.next() x = 0 while x<5: try: obj1 = obj.send(random.random()) except: pass x += 1
我們先給協程一個標准定義,即符合什么條件就能稱之為協程:
- 必須在只有一個單線程里實現並發
- 修改共享數據不需加鎖
- 用戶程序里自己保存多個控制流的上下文棧
- 一個協程遇到IO操作自動切換到其它協程
基於上面這4點定義,我們剛才用yield實現的程並不能算是合格的線程,不滿足最后一條。
如何實現單線程下並發效果:遇到IO操作就切換,協程之所以能處理大並發,就是由於擠掉了IO操作,使得CPU一直運行。
關鍵在於切換出來后,什么時候再切換回去??需要程序自動監測IO操作,IO操作結束就切換回去。
以上我們是通過yeild實現的協程的功能,yield能實現協程,不過實現過程不易於理解,greenlet是在這方面做了改進。
3、Greenlet
Greenlet是python的一個C擴展,來源於Stackless python,旨在提供可自行調度的‘微線程’, 即協程。generator實現的協程在yield value時只能將value返回給調用者(caller)。 而在greenlet中,target.switch(value)可以切換到指定的協程(target), 然后yield value。greenlet用switch來表示協程的切換,從一個協程切換到另一個協程需要顯式指定。是一種手動切換
4、gevent
gevent是第三方庫,可以輕松通過gevent實現並發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet全部運行在主程序操作系統進程的內部,但它們被協作式地調度。其基本思想是:
當一個greenlet遇到IO操作時,比如訪問網絡,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由於IO操作非常耗時,經常使程序處於等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在運行,而不是等待IO。
由於切換是在IO操作時自動完成,所以gevent需要修改Python自帶的一些標准庫,這一過程在啟動時通過monkey patch完成。