(2018-04-14.Python從Zero到One)三、網(wǎng)絡(luò)編程__3.4.7多任務(wù)實(shí)現(xiàn)-協(xié)程

上一篇文章為:→3.4.6epoll版-TCP服務(wù)器

協(xié)程

協(xié)程,又稱(chēng)微線程,纖程。英文名Coroutine。

協(xié)程是啥

首先我們得知道協(xié)程是啥?協(xié)程其實(shí)可以認(rèn)為是比線程更小的執(zhí)行單元。 為啥說(shuō)他是一個(gè)執(zhí)行單元,因?yàn)樗詭PU上下文。這樣只要在合適的時(shí)機(jī), 我們可以把一個(gè)協(xié)程 切換到另一個(gè)協(xié)程。 只要這個(gè)過(guò)程中保存或恢復(fù) CPU上下文那么程序還是可以運(yùn)行的。

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

協(xié)程和線程差異

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

協(xié)程的問(wèn)題

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

例子

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

那么這個(gè)實(shí)現(xiàn)有沒(méi)有問(wèn)題?

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

協(xié)程的好處

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

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

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

協(xié)程一個(gè)簡(jiǎn)單實(shí)現(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)

運(yùn)行結(jié)果:

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

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

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

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