python利用greenlet實現(xiàn)協(xié)程

什么是協(xié)程

首先我們得知道協(xié)程是啥?協(xié)程其實可以認為是比線程更小的執(zhí)行單元。 為啥說他是一個執(zhí)行單元,因為他自帶CPU上下文。這樣只要在合適的時機, 我們可以把一個協(xié)程 切換到另一個協(xié)程。 只要這個過程中保存或恢復(fù) CPU上下文那么程序還是可以運行的。

通俗的理解:在一個線程中的某個函數(shù),可以在任何地方保存當(dāng)前函數(shù)的一些臨時變量等信息,然后切換到另外一個函數(shù)中執(zhí)行,注意不是通過調(diào)用函數(shù)的方式做到的,并且切換的次數(shù)以及什么時候再切換到原來的函數(shù)都由開發(fā)者自己確定

協(xié)程和線程差異

那么這個過程看起來比線程差不多。其實不然, 線程切換從系統(tǒng)層面遠不止保存和恢復(fù) CPU上下文這么簡單。 操作系統(tǒng)為了程序運行的高效性每個線程都有自己緩存Cache等等數(shù)據(jù),操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復(fù)操作。 所以線程的切換非常耗性能。但是協(xié)程的切換只是單純的操作CPU的上下文,所以一秒鐘切換個上百萬次系統(tǒng)都抗的住。

協(xié)程的問題

但是協(xié)程有一個問題,就是系統(tǒng)并不感知,所以操作系統(tǒng)不會幫你做切換。 那么誰來幫你做切換?讓需要執(zhí)行的協(xié)程更多的獲得CPU時間才是問題的關(guān)鍵。

例子

目前的協(xié)程框架一般都是設(shè)計成 1:N 模式。所謂 1:N 就是一個線程作為一個容器里面放置多個協(xié)程。 那么誰來適時的切換這些協(xié)程?答案是有協(xié)程自己主動讓出CPU,也就是每個協(xié)程池里面有一個調(diào)度器, 這個調(diào)度器是被動調(diào)度的。意思就是他不會主動調(diào)度。而且當(dāng)一個協(xié)程發(fā)現(xiàn)自己執(zhí)行不下去了(比如異步等待網(wǎng)絡(luò)的數(shù)據(jù)回來,但是當(dāng)前還沒有數(shù)據(jù)到), 這個時候就可以由這個協(xié)程通知調(diào)度器,這個時候執(zhí)行到調(diào)度器的代碼,調(diào)度器根據(jù)事先設(shè)計好的調(diào)度算法找到當(dāng)前最需要CPU的協(xié)程。 切換這個協(xié)程的CPU上下文把CPU的運行權(quán)交個這個協(xié)程,直到這個協(xié)程出現(xiàn)執(zhí)行不下去需要等等的情況,或者它調(diào)用主動讓出CPU的API之類,觸發(fā)下一次調(diào)度。

那么這個實現(xiàn)有沒有問題?

其實是有問題的,假設(shè)這個線程中有一個協(xié)程是CPU密集型的他沒有IO操作, 也就是自己不會主動觸發(fā)調(diào)度器調(diào)度的過程,那么就會出現(xiàn)其他協(xié)程得不到執(zhí)行的情況, 所以這種情況下需要程序員自己避免。這是一個問題,假設(shè)業(yè)務(wù)開發(fā)的人員并不懂這個原理的話就可能會出現(xiàn)問題。

協(xié)程的好處

在IO密集型的程序中由于IO操作遠遠慢于CPU的操作,所以往往需要CPU去等IO操作。 同步IO下系統(tǒng)需要切換線程,讓操作系統(tǒng)可以在IO過程中執(zhí)行其他的東西。 這樣雖然代碼是符合人類的思維習(xí)慣但是由于大量的線程切換帶來了大量的性能的浪費,尤其是IO密集型的程序。

所以人們發(fā)明了異步IO。就是當(dāng)數(shù)據(jù)到達的時候觸發(fā)我的回調(diào)。來減少線程切換帶來性能損失。 但是這樣的壞處也是很大的,主要的壞處就是操作被 “分片” 了,代碼寫的不是 “一氣呵成” 這種。 而是每次來段數(shù)據(jù)就要判斷 數(shù)據(jù)夠不夠處理哇,夠處理就處理吧,不夠處理就在等等吧。這樣代碼的可讀性很低,其實也不符合人類的習(xí)慣。

但是協(xié)程可以很好解決這個問題。比如 把一個IO操作 寫成一個協(xié)程。當(dāng)觸發(fā)IO操作的時候就自動讓出CPU給其他協(xié)程。要知道協(xié)程的切換很輕的。 協(xié)程通過這種對異步IO的封裝 既保留了性能也保證了代碼的容易編寫和可讀性。在高IO密集型的程序下很好。但是高CPU密集型的程序下沒啥好處。

協(xié)程一個簡單實現(xiàn)(利用生成器)

import time

def A():
    while True:
        print("----A---")
        yield
        time.sleep(0.5)

def B(c):
    while True:
        print("----B---")
        c.next()
        time.sleep(0.5)

if __name__=='__main__':
    a = A()
    B(a)

輸出

--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--

使用greenlet來實現(xiàn)協(xié)程

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)

#創(chuàng)造兩個greenlet對象
gr1 = greenlet(test1)
gr2 = greenlet(test2)

#切換到gr1執(zhí)行
gr1.switch()

執(zhí)行結(jié)果:

-----A-----
----B----
-----A-----
----B----
-----A-----
----B----
-----A-----
----B----
-----A-----
----B----
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容