當我們要完成一個簡單的任務的時候,我們通常就是按照事件的先後順序按照過程編程,同頭到尾程序逐行執行,當我們需要實現的功能越複雜對性能要求越高時我們開始慢慢引入了多線程、多進程、協程等概念,多線程和多進程這個比較好理解,但是協程可能平時就見的不多了,本文對Python 協程到底是什麼做出簡介。以下全是個人理解如有不妥請多多指正。
什麼是協程
根據維基百科給出的定義,“協程 是爲非搶佔式多任務產生子程序的計算機程序組件,協程允許不同入口點在不同位置暫停或開始執行程序”。聽上去感覺很難懂,這裏舉上一個小小的例子,多線程和多進程都是可以理解爲有多個CPU在確實的運行相應的程序,程序並沒有發生中斷,而協程是運行發生中斷的,何時發生中斷是由用戶決定,產生中斷後當前的函數停止工作轉而去執行另外一個函數,這個對於嵌入式的中斷有點這種意思,單片機當中現在正在運行main函數,這個時候外部中斷觸發了,main暫時停止轉而去執行中斷函數,這個時候可能會出現一個想法這不在函數裏面調用另外一個函數也是實現的相同功能嗎,注意這只是說協程表現出來的一種形式,協程是協調個部分代碼達到資源最大利用,這纔是真正的協程,在協程中要有任務的安排調整。
講個故事可能來的比較快一點,現在我們要做的一件事那就是安裝Ubuntu系統,之前通過問別人我們大概知道了操作流程是先下載鏡像文件,然後在進製作啓動盤然後吧啦吧啦一系列操作,好現在我們就用一個單線程的人來模擬這個過程,首先我們進入一個操作也就是程序的一個函數,我們先下載鏡像,先打開網站之後一頓操作過後就顯示下載進度條了,注意這個動作(想象成程序的函數)還沒有執行完,因爲我們還沒有下載完整個鏡像,這個時候我們就開始等進度條,一秒過去了沒下載完,我們再等等,兩秒過去了還沒下載完,這個時候我們就開始不耐煩了,我看進度條顯示下載還剩餘10分鐘,我們好像可以乾點別的事情,等下載完過後再來操作,我們這個時候就不再等待了下載了,轉而去網站上看等會下載完成了怎麼製作啓動盤,這個時候我們就實現了任務的調度,從下載鏡像轉到了流量網頁。協程就是這樣發生在一個可能發生長時間阻塞的地方,我們不是讓CPU做無用的等待,而是讓CPU在等待的時間乾點其他有用的事情,我們手動進行任務切換的過程就是協程。
代碼實現
Python的協程經歷了很長的發展,由於只是簡介,只介紹最常用也是最簡單的一種用法
我們編寫一個程序模擬剛纔的過程
首先採用最我們用延遲模擬這個過程的耗時,最後統計總的耗時時間
import time
def download_ubuntu():
time.sleep(5)
# 查閱相關操作的教程
def look_up_how_to_operate():
time.sleep(3)
def install_ubuntu():
download_ubuntu()
look_up_how_to_operate()
def main():
start = time.time()
install_ubuntu()
end = time.time()
print ('Cost {} seconds'.format(end - start))
if __name__ == "__main__":
main()
運行這段代碼,我們得到下面的運行時間,無疑就是各個操作時間的累加
Cost 8.004846334457397 seconds
下面我們開始優化過程
import asyncio
import time
# 用async表示這個是一個異步的函數表示這個函數是可發生中斷的
async def download_ubuntu():
# await後面需要解釋一個可等待的對象,可以等待對象可以理解爲async聲明的函數或者具有__await__ 屬性的對象,這個時候保存函數的狀態跳出函數轉而去執行其他的操作
await asyncio.sleep(5)
async def look_up_how_to_operate():
await asyncio.sleep(3)
def install_ubuntu():
# 創建一個事件循環進而實現任務的調度
loop = asyncio.get_event_loop()
tasks = [download_ubuntu(),look_up_how_to_operate()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
def main():
start = time.time()
install_ubuntu()
end = time.time()
print ('Cost {} seconds'.format(end - start))
if __name__ == "__main__":
main()
通過計時我們看到時間明顯就縮短了,將其中的3s就優化出來了,是不是很神奇。
Cost 5.0030436515808105 seconds
應該是在Python 3.5之後提出了這種新的async/await
的操作,在以前的操作中協程還是很麻煩的,但是這種提供了很方便的操作,這個例子或許不是特別的合適下一篇博客會在ROS的實踐中繼續講解協程。在本文末尾會對代碼做出簡要的講解。
總結
總結起來協程就是實現用戶自己編寫代碼級別的任務的調度。發生在我們需要調用其它模塊的函數,這個函數是請求其他模塊的一個函數,我們對本線程的任務做出相應的調整。
將一個函數聲明爲async
就表示這個函數是一個可以被中斷的異步函數,在產生中斷過後去執行其他的函數。await
關鍵字表示等待這個步驟完成,這個關鍵字後面跟的必須是一個可以等待的對象不然就會報錯。