【Python - asyncio】非同步 I/O 簡介

更新於 發佈於 閱讀時間約 6 分鐘
raw-image


為什麼需要非同步? 我們在「【Web微知識系列】 Web Workers」有介紹到在瀏覽器可執行腳本Javascript環境底下如何完成非同步的操作, 主要是為了讓任務更有效率的進行, 不會因為一個非常耗時的工作堵塞住整個服務, 導致無法服務他人的窘境。


大家應該經常在餐廳裡會看到服務員協助顧客點餐吧! 而服務員的工作除了送餐、清潔之外還要協助顧客點餐, 那麼當每位顧客都花費10分鐘在思考要點什麼時, 服務員若在原地等待是一個非常浪費資源的行為, 因此通常每位服務員都先將顧客帶到位子上之後, 說明餐點跟給予菜單之後, 便可以釋出資源去服務其他客人, 甚至能夠完成其他工作, 這就是一個在我們生活中常常出現的非同步情境。


而Python在3.4版之後就有 asyncio 模組了, 但相關的API函數尚未完全, 因此使用起來並非那麼直觀, 而在3.8之後的功能就加入了許多語法糖封裝, 讓我們能夠很簡易的使用這些功能完成我們的非同步應用場景。


開發程式的過程中, 相信除了本身程式的運算之外, 更多的是網路的傳輸(外部API)、硬碟的存取(I/O), 而這些外部的服務都不應該阻塞我們的程式, 因此我們可以透過 asyncio 來做一個任務委外的動作, 而程式本身的運算也能夠持續執行, 這就是非同步的魅力所在。


概念建立之後, 我們就可以開始進行簡易的實作囉! 就讓我們一同探索非同步的 asyncio 吧!


協程 (Coroutine)

協程是一個比較特殊的函數, 也是為了解決單執行緒應用之中的等待浪費資源問題, 與常規函數不同,協程可以在適當的時機暫停、恢復和交互執行,這種能力使得協程特別適合處理非同步的任務,比如 I/O 操作、網絡請求等需要等待的工作。


async與await

這兩個語法糖主要是讓我們更直觀的標示異步的函式與執行的進入點。

  • async:用來宣告函數能夠有異步的功能。
  • await:用來標記異步的執行。

# async 宣告一個非同步函式
async def do(...):
...


...
...
# await 將控制權交給事件循環並等待結果回傳
await do(...)


這些語法想必常常在異步的Python程式中隨處所見吧, 這也是整個 asyncio 用來標示非同步函式的主要標誌。


關於事件循環

raw-image

回顧「【Web微知識系列】 Web Workers」 我們有提到事件循環的概念, 我們還是以餐廳服務員為例, 當我們收到客人的菜單時, 會將菜單交給內場的夥伴們進行料理, 而服務員就可以趁著備料的時候進行其他工作項目(擦桌子、帶位…), 直到料理完成之後, 切換回來將美味的料理交給客人享用, 這整個餐廳的日常工作就像是一個事件循環一般。


那在 asyncio 的世界裡應該怎麼使用呢? 我們可以透過「asyncio.get_event_loop」來建立一個事件循環, 然後再將上述的 「async func」帶入, 直到事件循環結束, 如下:


import asyncio

async def job():
...
await ...

asyncio.run(job())


多個非同步任務Tasks

「asyncio.create_task( )」 相信這個函式我們常常在原始碼中會常常看到, 而它就是一個更大的非同步單元, 因為我們都知道 await 是一個等待的概念

我們來看底下的例子, 共會需要3秒來完成整個事件,那這樣其實沒有得到非同步應用太大的好處, 我們是希望兩個echo之間是能夠在空閒時切換進行, 節省整體耗時。


import asyncio
import time
async def echo(msg, delay):
await asyncio.sleep(delay)
print(msg)
async def main():
start_time = time.time()
await echo('此作業需要1秒', 1)
await echo('此作業需要2秒', 2)
end_time = time.time()
execution_time = end_time - start_time
print(f'共計 {execution_time} 秒')
asyncio.run(main())


# python test.py
# 此作業需要1秒
# 此作業需要2秒
# 共計 3.003565788269043 秒


因此我們可以針對此情境進行優化, 透過任務的概念, 將兩個任務加入事件循環的Loop, 兩者互不堵塞, 讓任務執行最佳化, 我們可以看到共耗時約2秒左右。


import asyncio
import time
async def echo(msg, delay):
await asyncio.sleep(delay)
print(msg)

async def main():
start_time = time.time()
task1 = asyncio.create_task(
echo('此作業需要1秒', 1))
task2 = asyncio.create_task(
echo('此作業需要2秒', 2))
await task1
await task2
end_time = time.time()
execution_time = end_time - start_time
print(f'共計 {execution_time} 秒')
asyncio.run(main())

# python test.py
# 此作業需要1秒
# 此作業需要2秒
# 共計 2.0014331340789795 秒


結語

本章節主要是簡單的介紹一下Python的非同步概念, 它的精華之處在於讓我們能夠善用每個時刻不浪費, 既然程式語言能夠有這樣的概念, 我們的人生是否就像個事件循環一般呢? 我們是否也能善用這樣的技巧來讓我們的人生更加的精彩且有效率呢?


接下來我們會持續對於非同步協程的部份進行詳細的探討, 讓我們善用協程開發出高效的應用軟體吧!

留言
avatar-img
留言分享你的想法!
avatar-img
阿Han的沙龍
129會員
283內容數
哈囉,我是阿Han,是一位 👩‍💻 軟體研發工程師,喜歡閱讀、學習、撰寫文章及教學,擅長以圖代文,化繁為簡,除了幫助自己釐清思路之外,也希望藉由圖解的方式幫助大家共同學習,甚至手把手帶您設計出高品質的軟體產品。
阿Han的沙龍的其他內容
2025/01/29
🤔 簡單且靜態就足夠了? 相信我們在開發Python應用程式的過程中, 常常會借用Enum來定義我們可能的選項, 就像顏色紅、綠、黃會有這樣的結構: class Color(str, Enum): RED = 'red' GREED = 'green' YELLOW = 'yel
Thumbnail
2025/01/29
🤔 簡單且靜態就足夠了? 相信我們在開發Python應用程式的過程中, 常常會借用Enum來定義我們可能的選項, 就像顏色紅、綠、黃會有這樣的結構: class Color(str, Enum): RED = 'red' GREED = 'green' YELLOW = 'yel
Thumbnail
2025/01/08
當我們的系統發展到一定程度時, 難免會面臨到正式上線的問題, 要如何讓維運更加簡易呢? 尤其隨著複雜的客製化配置的出現時, 我們應該如何有效的管理, 甚至驗證配置是否如預期資料型態、格式…, 而正好 pydantic 可以滿足這樣的需求, 就讓我們來看看怎麼使用吧! 需安裝的套件 pip i
Thumbnail
2025/01/08
當我們的系統發展到一定程度時, 難免會面臨到正式上線的問題, 要如何讓維運更加簡易呢? 尤其隨著複雜的客製化配置的出現時, 我們應該如何有效的管理, 甚至驗證配置是否如預期資料型態、格式…, 而正好 pydantic 可以滿足這樣的需求, 就讓我們來看看怎麼使用吧! 需安裝的套件 pip i
Thumbnail
2025/01/02
要如何使用unicorn啟動多個FastAPI服務, 歡迎參考我們的「【💊 Python的解憂錦囊 - FastAPI】如何啟動多個Workers」。 當我們試著設計帶入模組化時… 我們在「【💊 Python的解憂錦囊 - FastAPI】使用 lifespan 來共享資料與管理生命週期
Thumbnail
2025/01/02
要如何使用unicorn啟動多個FastAPI服務, 歡迎參考我們的「【💊 Python的解憂錦囊 - FastAPI】如何啟動多個Workers」。 當我們試著設計帶入模組化時… 我們在「【💊 Python的解憂錦囊 - FastAPI】使用 lifespan 來共享資料與管理生命週期
Thumbnail
看更多
你可能也想看
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
Thumbnail
前段時間我們有介紹「【Python 軍火庫🧨 - websockets】雙向溝通的渠道」, 這種方式可以達到基本的連線沒問題,但隨著資安意識的抬頭, 我們的websocket連線也會需要在通道之上進行加密, 那麼我們將根據使用情境來教您如何選用適當的連線。 Server端 我們的Serve
Thumbnail
前段時間我們有介紹「【Python 軍火庫🧨 - websockets】雙向溝通的渠道」, 這種方式可以達到基本的連線沒問題,但隨著資安意識的抬頭, 我們的websocket連線也會需要在通道之上進行加密, 那麼我們將根據使用情境來教您如何選用適當的連線。 Server端 我們的Serve
Thumbnail
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
當我們在撰寫一套系統的時候, 總是會提供一個介面讓使用者來觸發功能模組並回傳使用者所需的請求, 而傳統的安裝包模式總是太侷限, 需要個別主機獨立安裝, 相當繁瑣, 但隨著時代的演進與互聯網的崛起, 大部分的工作都可以藉由網頁端、裝置端來觸發, 而伺服端則是負責接收指令、運算與回傳結果, 雲端
Thumbnail
當我們在撰寫一套系統的時候, 總是會提供一個介面讓使用者來觸發功能模組並回傳使用者所需的請求, 而傳統的安裝包模式總是太侷限, 需要個別主機獨立安裝, 相當繁瑣, 但隨著時代的演進與互聯網的崛起, 大部分的工作都可以藉由網頁端、裝置端來觸發, 而伺服端則是負責接收指令、運算與回傳結果, 雲端
Thumbnail
Websocket是一種網路傳輸的協定,讓建立一次handshake的過程就可以相互傳遞資料,而非同步的過程能夠讓處理事情更有效率,這篇文章將帶你深入瞭解Websocket如何運作、以及其特點與優勢。
Thumbnail
Websocket是一種網路傳輸的協定,讓建立一次handshake的過程就可以相互傳遞資料,而非同步的過程能夠讓處理事情更有效率,這篇文章將帶你深入瞭解Websocket如何運作、以及其特點與優勢。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News