python gevent協程模組詳解

2020-08-12 00:26:37

進程、執行緒、協程區分

我們通常所說的協程Coroutine其實是corporate routine的縮寫,直接翻譯爲協同的例程,一般我們都簡稱爲協程。

在linux系統中,執行緒就是輕量級的進程,而我們通常也把協程稱爲輕量級的執行緒即微執行緒。

進程和協程

下面 下麪對比一下進程和協程的相同點和不同點:

相同點:

  1. 相同點存在於,當我們掛起一個執行流的時,我們要儲存的東西:
  2. 棧, 其實在你切換前你的區域性變數,以及要函數的呼叫都需要儲存,否則都無法恢復 暫存器狀態,這個其實用於當你的執行流恢復後要做什麼

而暫存器和棧的結合就可以理解爲上下文,上下文切換的理解:
CPU看上去像是在併發的執行多個進程,這是通過處理器在進程之間切換來實現的,操作系統實現這種交錯執行的機制 機製稱爲上下文切換

操作系統保持跟蹤進程執行所需的所有狀態資訊。這種狀態,就是上下文。
在任何一個時刻,操作系統都只能執行一個進程程式碼,當操作系統決定把控制權從當前進程轉移到某個新進程時,就會進行上下文切換,即儲存當前進程的上下文,恢復新進程的上下文,然後將控制權傳遞到新進程,新進程就會從它上次停止的地方開始。

不同點:

  1. 執行流的排程者不同,進程是內核排程,而協程是在使用者態排程,也就是說進程的上下文是在內核態儲存恢復的,而協程是在使用者態儲存恢復的,很顯然使用者態的代價更低
  2. 進程會被強佔,而協程不會,也就是說協程如果不主動讓出CPU,那麼其他的協程,就沒有執行的機會。
  3. 對記憶體的佔用不同,實際上協程可以只需要4K的棧就足夠了,而進程佔用的記憶體要大的多
  4. 從操作系統的角度講,多協程的程式是單進程,單協程

執行緒和協程

既然我們上面也說了,協程也被稱爲微執行緒,下面 下麪對比一下協程和執行緒:

  1. 執行緒之間需要上下文切換成本相對協程來說是比較高的,尤其在開啓執行緒較多時,但協程的切換成本非常低。
  2. 同樣的執行緒的切換更多的是靠操作系統來控制,而協程的執行由我們自己控制。

協程只是在單一的執行緒裡不同的協程之間切換,其實和執行緒很像,執行緒是在一個進程下,不同的執行緒之間做切換,這也可能是協程稱爲微執行緒的原因吧。

簡介

Gevent模組是一種基於協程的Python網路庫,它用到Greenlet提供的,封裝了libevent事件回圈的高層同步API。它讓開發者在不改變程式設計習慣的同時,用同步的方式寫非同步I/O的程式碼。

greenlets

如果想瞭解gevent的排程流程,最重要的是對greenlet有基本的瞭解。下面 下麪總結一些個人認爲比較重要的點:

  1. 每一個greenlet.greenlet範例都有一個parent(可指定,預設爲創生新的greenlet.greenlet所在環境),當greenlet.greenlet範例執行完邏輯正常結束、或者拋出異常結束時,執行邏輯切回到其parent
  2. 可以繼承greenlet.greenlet,子類需要實現run方法,當呼叫greenlet.switch方法時會呼叫到這個run方法

在gevent中,有兩個類繼承了greenlet.greenlet,分別是gevent.hub.Hub和gevent.greenlet.Greenlet。後文中,如果是greenlet.greenlet這種寫法,那麼指的是原生的類庫greentlet,如果是greenlet(或者Greenlet)那麼指gevent封裝後的greenlet。

gevent中的主要模式, 它是以C擴充套件模組形式接入Python的輕量級協程。 全部執行在主程式操作系統進程的內部,但它們被程式設計師共同作業式地排程。

在任何時刻,只有一個協程在執行。

區別於multiprocessing、threading等提供真正並行構造的庫, 這些庫輪轉使用操作系統排程的進程和執行緒,是真正的並行。

特點

基於libev的快速事件回圈(Linux上epoll,FreeBSD上kqueue)。
基於greenlet的輕量級執行單元。
API的概念和Python標準庫一致(如事件,佇列)。
可以配合socket,ssl模組使用。
能夠使用標準庫和第三方模組建立標準的阻塞通訊端(gevent.monkey)。
預設通過執行緒池進行DNS查詢,也可通過c-are(通過GEVENT_RESOLVER=ares環境變數開啓)。
TCP/UDP/HTTP伺服器
子進程支援(通過gevent.subprocess)
執行緒池

安裝

安裝和依賴
依賴於greenlet library
支援python 2.6+ 、3.3+

pip install gevent

範例

用greenlet執行一個函數

#coding:utf-8
import time
from  greenlet import greenlet

def eat():
    print('魔降風雲變 is eating')
    time.sleep(0.5)
    print('魔降風雲變 finished eat')

def sleep():
    print('小馬過河 is sleeping')
    time.sleep(0.5)
    print('小馬過河 finished sleep')
g1 = greenlet(eat)
g1.switch()

--------------結果:

魔降風雲變 is eating
魔降風雲變 finished eat

在一個任務函數中切換到另一個任務函數去執行,然後沒有再切換回來

#coding:utf-8
import time
from  greenlet import greenlet
def eat():
    print('魔降風雲變 is eating')
    g2.switch()
    time.sleep(0.5)
    print('魔降風雲變 finished eat')

def sleep():
    print('小馬過河 is sleeping')
    time.sleep(0.5)
    print('小馬過河 finished sleep')
g1 = greenlet(eat)
g2 = greenlet(sleep)
g1.switch()

--------------結果:

魔降風雲變 is eating
小馬過河 is sleeping
小馬過河 finished sleep

一個任務中切換到另一個任務,另一個任務執行完了再執行切換回到這個任務執行。實現兩個任務間切換並都執行結束

#coding:utf-8
import time
from  greenlet import greenlet
def eat():
    print('魔降風雲變 is eating')
    g2.switch()
    time.sleep(0.5)
    print('魔降風雲變 finished eat')

def sleep():
    print('小馬過河 is sleeping')
    time.sleep(0.5)
    print('小馬過河 finished sleep')
    g1.switch()
g1 = greenlet(eat)
g2 = greenlet(sleep)
g1.switch()

------------結果:

魔降風雲變 is eating
小馬過河 is sleeping
小馬過河 finished sleep
魔降風雲變 finished eat

建立協程任務

``python
#coding:utf-8
import time
import gevent
def eat():
    print('魔降風雲變 is eating')
    time.sleep(1)
    print('魔降風雲變  finished eat')
def sleep():
    print('小馬過河 is sleeping')
    time.sleep(1)
    print('小馬過河 finished sleep')
g1 = gevent.spawn(eat)  # 創造一個協程任務

-----------結果:沒有輸出
協程遇到阻塞才切換,這裏程式碼從上到下執行結束,沒有遇到阻塞

import time
import gevent
def eat():
    print('魔降風雲變 is eating')
    time.sleep(1)
    print('魔降風雲變  finished eat')
def sleep():
    print('小馬過河 is sleeping')
    time.sleep(1)
    print('小馬過河 finished sleep')
g1 = gevent.spawn(eat)  # 創造一個協程任務
gevent.sleep(2) #加個gevent.sleep(2)就切換到eat執行裏面的程式碼了

---------結果:

魔降風雲變 is eating
魔降風雲變  finished eat

#加個gevent.sleep(2)就切換到eat執行裏面的程式碼了。eat中間time.sleep(1)照樣睡。

同步和非同步執行

兩個子任務之間的 切換也就是上下文切換。
建立Greenlets,gevent對Greenlet初始化提供了一些封裝.
第一個

import gevent
def test1():
  print 12
  gevent.sleep(0)
  print 34
def test2():
  print 56
  gevent.sleep(0)
  print 78
gevent.joinall([
  gevent.spawn(test1),
  gevent.spawn(test2),
])
$ python gevent2.py
12
56
34
78

第二個

import gevent
from gevent import Greenlet
def foo(message, n):
    gevent.sleep(n)
    print(message)
    thread1 = Greenlet.spawn(foo, "Hello", 1)
    thread2 = gevent.spawn(foo, "I live!", 2)
    thread3 = gevent.spawn(lambda x: (x+1), 2)
    threads = [thread1, thread2, thread3]
    gevent.joinall(threads)

第二個

$ python gevent3.py
Hello
I live!

同步vs非同步

import gevent
import random
def task(pid):
    gevent.sleep(random.randint(0,2)*0.001)
    print('Task %s done' % pid)
def synchronous():
    for i in xrange(5):
        task(i)
def asynchronous():
    threads = [gevent.spawn(task, i) for i in xrange(5)]
    gevent.joinall(threads)
    print('Synchronous:')
    synchronous()
    print('Asynchronous:')
    asynchronous()
$ python gevent3.py
Synchronous:
Task 0 done
Task 1 done
Task 2 done
Task 3 done
Task 4 done
Asynchronous:
Task 2 done
Task 0 done
Task 1 done
Task 3 done
Task 4 done

參考連線:
https://www.cnblogs.com/machangwei-8/p/10907572.html
https://softlns.github.io/2015/11/28/python-gevent/

http://codingdict.com/blog/article/2019/7/11/3560.html