Python加速技巧|異步處理(Asynchronous)
在一開始開發程式時,「自動」執行批次任務相對比「手動」下,怎麼樣都覺得很有效率,但當程式開發到一定程度時,某些專案就會開始要求速度,例如我近幾個月都在開發選擇權造市程式,這個時候執行速度就是相當重要啦!
一般正常狀態撰寫的程式是由上而下的線性執行,當執行無誤完成第一步後,才會接著執行下一步。如果任務之間沒有相依性,我們可以如何增加效率呢?
我們先舉一個生活上的例子——「煮飯」,近期作為外派菜鳥的我正在學習如何自己煮飯,畢竟在台灣的都會區生活實在太方便了,到了異國只能自己來才吃得到想吃的味道。而我目前的階段屬於煮飯菜鳥,一次不能做太多事,免得手忙腳亂全部搞砸,所以一次只能做一件事情,因為很多事情都沒做過也不知道時間處理會如何。
Lv 1 廚房菜鳥:(Single thread)
- 一次處理一件任務:例如先洗菜,再煮一鍋水,再把菜放下去燙;洗米、泡米、再放入電鍋煮。
- 假設每件任務都是獨立且依序執行
Lv 2 廚房進階菜鳥:(Asynchronous)
- 區別什麼任務會花比較久的時間,但又不用自己實際盯場,例如電鍋煮飯。
- 當在執行等待任務完成的時間,就可以先去做其他任務,例如洗完米按下電鍋後,就可以來準備煎荷包蛋或是其餘備料,等飯煮好時,再回來處理飯的其他流程。
Lv 3 廚房老手:(Multiple threads & Asynchronous)
- 可以區分什麼任務會花比較久的等待時間。
- 可同時處理多項任務,例如可以同時煮麵跟煎荷包蛋,但必須要有多個瓦斯爐或鍋子。
😂 這些都是我在煮飯時悟出的程式執行道理!
回到Python的世界,我們先使用廚房菜鳥的模式,我們的範例是煮咖啡與烤貝果,如下面的範例,煮咖啡要3分鐘,而烤貝果則需要5分鐘,如果我們一項接著一項做,總共需要8分鐘左右才能吃到一個早餐組合。
一般模式(非使用Async):
import time
def brew_coffee():
print("Start brew coffee")
time.sleep(3 * 60)
print("End brew coffee")
return "coffee ready"
def toast_bagel():
print("Start toast bagel")
time.sleep(5 * 60)
print("End toast bagel")
return "bagel toasted"
def main():
start_time = time.time()
result_coffee = brew_coffee()
result_bagel = toast_bagel()
end_time = time.time()
elapsed_time = end_time - start_time
print(f"Result of brew_coffee: {result_coffee}")
print(f"Result of toast bagel: {result_bagel}")
print(f"Total execution time: {elapsed_time:2f} seconds")
if __name__ == "__main__":
main()
第二種模式比較屬於正常人的模式,我們知道咖啡機可能要花一些時間預備,所以啟動後,這時候我們也趕快把貝果從冰箱拿出來放到烤箱,並將時間設定5分鐘,就讓烤箱開始烤,而咖啡機這時候可能也差不多好了,再回到咖啡機去處理磨豆等任務,當烤箱叮的一聲完成後,咖啡與烤貝果等任務其實也完成了,這個就是Async的概念。
Async模式
import asyncio
import time
async def brew_coffee():
print("Start brew coffee")
await asyncio.sleep(3 * 60)
print("End brew coffee")
return "coffee ready"
async def toast_bagel():
print("Start toast bagel")
await asyncio.sleep(5 * 60)
print("End toast bagel")
return "bagel toasted"
async def main():
start_time = time.time()
# method 1: asyncio.gather()
batch = asyncio.gather(brew_coffee(), toast_bagel())
result_coffee, result_bagel = await batch
# method 2: asyncio.create_task()
# coffee_task = asyncio.create_task(brew_coffee())
# toast_task = asyncio.create_task(toast_bagel())
# result_coffee = await coffee_task
# result_toast = await toast_task
end_time = time.time()
elapsed_time = end_time - start_time
print(f"Result of brew_coffee: {result_coffee}")
print(f"Result of toast bagel: {result_bagel}")
print(f"Total execution time: {elapsed_time:2f} seconds")
if __name__ == "__main__":
asyncio.run(main())
Asynchronous是近年比較常使用的加速處理模式,主要用來加速多項耗時較長的任務,例如資料庫I/O操作與爬蟲等任務,而這兩項任務都是等待其他服務的回應,而非本機端在進行處理。所以想透過Async加速的第一步就是識別專案的任務有哪些,個別分析耗時的狀況。
另一個系列加速處理的就是multiprocessing和threading,這兩項屬於平行同步處理,可能會導致一些資源消耗或併發一些問題,例如瞬間CPU達到極限或是記憶體佔用率過高,使得全部程序都停擺,而我自己的選擇權造市則使用混合模式執行。