首頁  >  文章  >  後端開發  >  深入了解python中的協程函數

深入了解python中的協程函數

零到壹度
零到壹度原創
2018-04-14 11:16:161728瀏覽

這篇文章給大家分享的內容是深入了解python中的協程函數,有著一定的參考價值,有需要的朋友可以參考一下

概念:

根據維基百科給出的定義,「協程是為非搶佔式多任務產生子程式的電腦程式元件,協程允許不同入口點在不同位置暫停或開始執行程式」。從技術的角度來說,「協程就是你可以暫停執行的函數」。如果你把它理解成“就像生成器一樣”,那麼你就想對了。

協程,又稱為微線程,看上去像是子程序,但是它和子程序又不太一樣,它在執行的過程中,可以在中斷當前的子程序後去執行別的子程序,再返回來執行之前的子程序,但是它的相關資訊還是之前的。

協程不同於執行緒,執行緒是搶佔式的調度,而協程是協同式的調度,協程則需要自己做調度。 
子程式呼叫總是一個入口,一次返回,呼叫順序是明確的。而協程的呼叫和子程序不同。協程看起來也是子程序,但執行過程中,在子程序內部可中斷,然後轉而執行別的子程序,在適當的時候再返回來接著執行。

協程的優點:

  • 協程優勢是極高的執行效率。因為子程式切換不是執行緒切換,而是由程式本身控制,因此,沒有執行緒切換的開銷,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯。用來執行協程多任務非常適合。

  • 協程沒有執行緒的安全性問題。一個行程可以同時存在多個協程,但是只有一個協程是啟動的,而且協程的啟動和休眠又是程式設計師透過程式設計來控制,而不是作業系統控制的。

產生器實作協程原理

#範例:

def func(n):
    index=0
    if index<=n:
        c=yield 1
        print("task------{}".format(c))
        index+=1f=func(3)
n=next(f)
print(n)try:
    n=f.send(5)#程序就直接结束了
    print("n是{}".format(n))except StopIteration as e:    pass
输出打印:1task------5

解釋說明:

  • 很明顯func是一個產生器,send方法有一個參數,該參數指定的是上一次被掛起的yield語句的回傳值。

  • send需要做例外處理。

  • 總的來說,send方法和next方法唯一的差別在於執行send方法會先把上一次掛起的yield語句的回傳值透過參數設定,從而實現與生成器方法的交互作用。但要注意,在一個生成器物件沒有執行next方法之前,由於沒有yield語句被掛起,所以執行send方法會報錯。

  • send方法的參數為None時,它與next方法完全等價。

生成器實作生產者與消費者模式:

def cunsumer():
    while True:
        n=yield 3
        if not n:            return
        print(&#39;cunsumer{}&#39;.format(n))def product(c):
    c.send(None)
    n=0
    while n<5:
        n=n+1
        r=c.send(n)
        print("product{}".format(r))
    c.close()
c=cunsumer()
product(c)
打印:
cunsumer1
product3
cunsumer2
product3
cunsumer3
product3
cunsumer4
product3
cunsumer5
product3

說明:

在生產者先執行了c.send(None),目的是先讓消費者掛起,再用send傳值,第一次傳1,消費者那裡打印1,生產者打印r是消費者yield後面的值。

greenlet 的引入

雖然CPython(標準Python)能夠透過生成器來實現協程,但使用起來還並不是很方便。

同時,Python的一個衍生版 Stackless Python實作了原生的協程,它更有利於使用。

於是,大家開始將 Stackless 中關於協程的程式碼  單獨拿出來做成了CPython的擴充包。

這就是 greenlet 的由來,因此 greenlet 是底層實作了原生協程的 C擴充函式庫。

程式碼示意:

from greenlet import greenletimport randomimport timedef Producer():
    while True:
        item = random.randint(0,10)
        print("生产了{}".format(item))
        c.switch(item)#切换到消费者,并将item传入消费者
        time.sleep(1)def consumer():
    print(&#39;我先执行&#39;)    #p.switch()
    while True:
        item = p.switch()#切换到生产者,并且等待生产者传入item
        print(&#39;消费了{}&#39;.format(item))
c = greenlet(consumer)#将一个普通函数变成一个协程p = greenlet(Producer)
c.switch()#让消费者先进入暂停状态(只有恢复了才能接收数据)

greenlet 的價值:

  • 高效能的原生協程

  • 語意更明確的明確切換

  • #直接將函數包裝成協程,保持原始程式碼風格

#gevent協程

雖然,我們有了基於epoll 的回呼程式設計模式,但卻很難使用。

即使我們可以透過配合 生成器協程 進行複雜的封裝,以簡化程式設計難度。
但仍然有一個大的問題: 封裝難度大,現有程式碼幾乎完全要重寫

gevent,透過封裝了 libev(基於epoll) 和 greenlet 兩個函式庫。
幫我們做好封裝,讓我們以類似執行緒的方式使用協程。

以至於我們幾乎不用重寫原來的程式碼就能充分利用 epoll 和 協程 威力。

程式碼示意:

from gevent import monkey;monkey.patch_all()#会把python标准库当中一些阻塞操作变成非阻塞import geventdef test1():
    print("11")
    gevent.sleep(4)#模拟爬虫请求阻塞
    print("33")def test2():
    print("22")
    gevent.sleep(4)
    print("44")
gevent.joinall([gevent.spawn(test1),gevent.spawn(test2)])#joinall 阻塞当前协程,执行给定的greenlet#spawn 启动协程,参数就是函数的名字

gevent 的價值:

遇到阻斷就切換到另一個協程繼續執行!

  • 使用基於 epoll 的 libev 來避免阻斷。

  • 使用基於 gevent 的 高效能協程 來切換執行。

  • 只在遇到阻塞的時候切換,沒有輪需的開銷,也沒有執行緒的開銷。

gevent實作並發伺服器

from gevent import monkey;monkey.patch_all()  #建议放在首行,会把python标准库当中一些阻塞操作变成非阻塞import geventimport socket


server=socket.socket()
server.bind((&#39;&#39;,6666))
server.listen(5)
print("开始监听")def readable(con,addr):
    print("客户端{}接入".format(addr))    while True:
        data=con.recv(1024)        if data:
            print(data)        else:
            con.close()            breakwhile True:
    con,addr=server.accept()
    gevent.spawn(readable,con,addr)#将readable函数变为协程,并且把con和addr传入其中。

gevent 協程通訊

gevent也有自己的佇列。使用方式和進線程基本上一樣。

基於gevent和佇列的生產者和消費者模式

from gevent import monkey;monkey.patch_all()import geventfrom gevent.queue import Queueimport randomdef producter(queue):
    while True:
        item=random.randint(0,99)
        print(&#39;生产了{}&#39;.format(item))
        queue.put(item)
        gevent.sleep(1)def comuser(queue):
    while True:
        item=queue.get()
        print(&#39;消费了{}&#39;.format(item))
queue=Queue()
p=gevent.spawn(producter,queue)
c=gevent.spawn(comuser,queue)
gevent.joinall([p,c])
打印:
生产了33消费了33生产了95消费了95生产了92消费了92...


相关推荐:

python中多进程+协程的使用

python中协程

Python 协程的详细用法和例子

python 协程示例

以上是深入了解python中的協程函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn