1. 程式人生 > >python中協程實現的本質以及兩個封裝協程模組greenle、gevent

python中協程實現的本質以及兩個封裝協程模組greenle、gevent

協程

協程,又稱微執行緒,纖程。英文名Coroutine。

協程是啥

協程是python箇中另外一種實現多工的方式,只不過比執行緒更小佔用更小執行單元(理解為需要的資源)。 為啥說它是一個執行單元,因為它自帶CPU上下文。這樣只要在合適的時機, 我們可以把一個協程 切換到另一個協程。 只要這個過程中儲存或恢復 CPU上下文那麼程式還是可以執行的。

通俗的理解:在一個執行緒中的某個函式,可以在任何地方儲存當前函式的一些臨時變數等資訊,然後切換到另外一個函式中執行,注意不是通過呼叫函式的方式做到的,並且切換的次數以及什麼時候再切換到原來的函式都由開發者自己確定

協程和執行緒差異

在實現多工時, 執行緒切換從系統層面遠不止儲存和恢復 CPU上下文這麼簡單。 作業系統為了程式執行的高效性每個執行緒都有自己快取Cache等等資料,作業系統還會幫你做這些資料的恢復操作。 所以執行緒的切換非常耗效能。但是協程的切換隻是單純的操作CPU的上下文,所以一秒鐘切換個上百萬次系統都抗的住。

簡單實現協程

利用yield關鍵字(其實質就是一個生成器,有關於生成器的詳細解析:python生成器詳解

import time
​
def test1():
    while True:
        print("----test1---")
        yield
        time.sleep(0.5)
​
def test2():
    while True:
        print("----test2---")
        yield
        time.sleep(0.5)
​
def main():
    t1 = test1()
    t2 
= test2() while True: next(t1) next(t2) ​ if __name__ == "__main__": main()

 

執行結果:

----test1---
----test2---
----test1---
----test2---
----test1---
----test2---
----test1---
----test2---
----test1---
----test2---
----test1---
----test2---
...省略...

 

greenlet

為了更好使用協程來完成多工,python中的greenlet模組對其封裝,從而使得切換任務變的更加簡單

安裝方式

使用如下命令安裝greenlet模組:

sudo pip3 install greenlet


#coding=utf-8
from greenlet import greenlet
import time
​
def test1():
    while True:
        print "---A--"
        gr2.switch()
        time.sleep(0.5)
​
def test2():
    while True:
        print "---B--"
        gr1.switch()
        time.sleep(0.5)
​
gr1 = greenlet(test1)
gr2 = greenlet(test2)
​
#切換到gr1中執行
gr1.switch()

 

執行效果

---A--
---B--
---A--
---B--
---A--
---B--
---A--
---B--
...省略...

 

gevent

greenlet已經實現了協程,但是這個還的人工切換,是不是覺得太麻煩了,不要捉急,python還有一個比greenlet更強大的並且能夠自動切換任務的模組gevent

其原理是當一個greenlet遇到IO(指的是input output 輸入輸出,比如網路、檔案操作等)操作時,比如訪問網路,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。

由於IO操作非常耗時,經常使程式處於等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在執行,而不是等待IO

安裝

pip3 install gevent

 

1. gevent的使用

import gevent
​
def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
​
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

 

執行結果

<Greenlet at 0x10e49f550: f(5)> 0
<Greenlet at 0x10e49f550: f(5)> 1
<Greenlet at 0x10e49f550: f(5)> 2
<Greenlet at 0x10e49f550: f(5)> 3
<Greenlet at 0x10e49f550: f(5)> 4
<Greenlet at 0x10e49f910: f(5)> 0
<Greenlet at 0x10e49f910: f(5)> 1
<Greenlet at 0x10e49f910: f(5)> 2
<Greenlet at 0x10e49f910: f(5)> 3
<Greenlet at 0x10e49f910: f(5)> 4
<Greenlet at 0x10e49f4b0: f(5)> 0
<Greenlet at 0x10e49f4b0: f(5)> 1
<Greenlet at 0x10e49f4b0: f(5)> 2
<Greenlet at 0x10e49f4b0: f(5)> 3
<Greenlet at 0x10e49f4b0: f(5)> 4

 

可以看到,3個greenlet是依次執行而不是交替執行

2. gevent切換執行

import gevent
​
def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        #用來模擬一個耗時操作,注意不是time模組中的sleep
        gevent.sleep(1)
​
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

 

執行結果

<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4

 

3. 給程式打補丁

from gevent import monkey
import gevent
import random
import time
​
def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())
​
gevent.joinall([
        gevent.spawn(coroutine_work, "work1"),
        gevent.spawn(coroutine_work, "work2")
])

 

執行結果

work1 0
work1 1
work1 2
work1 3
work1 4
work1 5
work1 6
work1 7
work1 8
work1 9
work2 0
work2 1
work2 2
work2 3
work2 4
work2 5
work2 6
work2 7
work2 8
work2 9

 

 
from gevent import monkey
import gevent
import random
import time
​
# 有耗時操作時需要
monkey.patch_all()  # 將程式中用到的耗時操作的程式碼,換為gevent中自己實現的模組
def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())
​
gevent.joinall([
        gevent.spawn(coroutine_work, "work1"),
        gevent.spawn(coroutine_work, "work2")
])

 

 

執行結果

work1 0
work2 0
work1 1
work1 2
work1 3
work2 1
work1 4
work2 2
work1 5
work2 3
work1 6
work1 7
work1 8
work2 4
work2 5
work1 9
work2 6
work2 7
work2 8
work2 9