什么是協(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----