iT邦幫忙

2

python的asyncio模組(二):異步程式設計基本概念

在說明asyncio的基本用法之前,先來說明一下異步程式設計的幾項基本概念:

  1. 事件迴圈(event loop)
  2. 事件(event)
  3. 回調函數(callback)

先來思考一下為什麼異步程式設計為什麼要存在:

  1. 有多個任務需要同時執行

多個任務可以同時進行才符合一般人使用電腦的需求,假設我今天打開電腦,我想要一邊聽音樂一邊看網頁,播放音樂是一個任務,顯示網頁又是一個任務,若任務無法同時進行的話,難道要先把音樂放完才能看網頁嗎?

雖然現在CPU是多核心,但打開工作管理員會發現任務實在太多了,所以通常一核心的CPU要同時處理多件事情,那就只能讓任務各種穿插執行了,讓人腦感覺好像是同時進行一樣,像下面這張圖:

https://ithelp.ithome.com.tw/upload/images/20180930/20107274SCvIXGO0Vk.jpg
來源網址:https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/

  1. io調度時間比起CPU執行指令的時間實在是太長了

從上一篇文章python的asyncio模組(一):異步執行的好處就能知道io時間比CPU執行時間要花費更久,所以為了不要讓CPU去等待io而浪費時間,我們就可以趁這段時間去切換執行其他任務,比如說播放音樂,從本地端或是網路上讀取音樂檔就是需要調動io,這時就可以利用中間小段空閒時間去開啟並顯示網頁,等到io調動時間結束,就可以重新執行原本任務了。

一、事件迴圈(Event loop)

既然異步程式可以在多個任務之間切換,那想必這個程式的架構裡一定有一個任務的list,這樣程式才知道有什麼樣的任務可以做切換,而這個任務的list以及中間切換的機制,就是由事件迴圈(Event loop)來處理。

二、事件(Event)與回調函數(Callback)

現在Event loop裏面有一個list,若程式有一些任務需要以異步的方式去執行,那就需要以"Event:Callback"的型式註冊進我們Event loop的list裏面,之後Event loop以for迴圈的方式去察看list裏面的Event是否發生,若發生了就執行相對應的Callback,並註銷這個Event的監聽。

打個比方,我今天對Event loop註冊了三個任務:

  1. "Event_A:Callback_A"
  2. "Event_B:Callback_B"
  3. "Event_C:Callback_C"

然後Event loop會如以下的方式循環的對list裡面的事件做監聽:

https://ithelp.ithome.com.tw/upload/images/20180930/20107274GdFfJAAMGR.png

假設正在監聽的Event_B發生了,就會註銷Event_B的監聽(也就是把Event_B從list中移除),然後執行Callback_B,執行完後在繼續監聽剩下的事件。

https://ithelp.ithome.com.tw/upload/images/20180930/20107274uhbfY2Zvg8.png

但上圖的解釋可能會出現一個疑問,裡面的意思是執行完Callback_B之後才會繼續監聽,但假設在執行Callback_B這個任務的中途,出現了需要調動io的指令,不就應該要先放下Callback_B這個任務,去監聽其他事件並執行其他任務嗎?為什麼要等到Callback_B執行完呢?

沒錯,異步程式設計應該要在遇到io讀取的時候切換其他任務去執行,但上圖確實是Event_loop的實作機制,所以如果你的Callback_B的內容像以下這樣:

def Callback_B():
    do_some_work1()
    read_from_io_and_wait() # 讀取io的指令,等到讀取完成才能執行下一個指令
    do_some_work2()
    return

你的程式是沒有辦法中途去切換其他任務的,若要能夠切換其他任務,應該要設計成以下型式:

def Callback_B():
    do_some_work1()
    read_from_io_and_not_wait() # 讀取io的指令,不用等到讀取完成就直接執行下一個指令
    register_to_EventLoop("finish read from io",Callback_D)
    return

def Callback_D(): 
    # 等到Callback_B讀取io的指令完成並被EventLoop監聽到就執行
    do_some_work2()
    return

事實上如果Callback_B若要有遇到調動io指令就暫停執行的功能,那他應該要設計成遇到調動io指令,就多註冊一個"Event_D:Callback_D"進去Event loop list,這個Event_D指的是"調動io完成",Callback_D的任務範圍是完成調動io"後"所要執行的指令,而Callback_B的任務範圍應該只有完成調動io"前"所要執行的指令。

https://ithelp.ithome.com.tw/upload/images/20180930/20107274w2dzJl2aj6.png

參考資料:
Event loop的實作細節
http://lotabout.me/2017/understand-python-asyncio/


1 則留言

0
cool21540125
iT邦新手 5 級 ‧ 2019-04-02 15:07:50

最後一張圖, 最右邊的 Event Loop List
Event_B 應該換成 Event_C ?

我要留言

立即登入留言