【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
118會員
266內容數
哈囉,我是阿Han,是一位 👩‍💻 軟體研發工程師,喜歡閱讀、學習、撰寫文章及教學,擅長以圖代文,化繁為簡,除了幫助自己釐清思路之外,也希望藉由圖解的方式幫助大家共同學習,甚至手把手帶您設計出高品質的軟體產品。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
阿Han的沙龍 的其他內容
我們在處理音檔時常常會使用到 [soundfile](https://pypi.org/project/soundfile/) 這套工具, 當我們試圖讀取檔案時卻發生了這樣的錯誤訊息… TypeError: Not allowed for existing files (except 'RAW')
關於多執行緒/多行程的使用方式 在Python 3.2版本之後加入了「concurrent.futures」啟動平行任務, 它可以更好的讓我們管理多執行緒/多行程的應用場景,讓我們在面對這種併發問題時可以不必害怕, 用一個非常簡單的方式就能夠處裡, 底下我們將為您展示一段程式碼: imp
這個問題發生在我們開發Python的Websocket Server時, 使用以下的程式碼架設服務 start_server = websockets.serve(server, 'localhost', args.port) async with start_server:
假設我們今天想要訓練一個AI模型, 那麼我們會有一批大型資料集, 通常會根據比例來切分三個模型訓練所需的訓練集(train)、驗證集(dev)、測試集(test), 而我們本次會示範一下Python如何對一個List清單進行切分, 基本上大同小異, 我們只要掌握作法即可概念相通。 任務提示
我們在使用Python語言進行軟體開發時, 常常會需要dict這個資料結構來儲存複雜結構的資料, 就如同JSON一般, 我們會具有這樣的Key/Value模式組成的資料結構, 如下圖: 而當我們在Python的世界裡, 除了嚴謹規範資料欄位的@dataclass之外, 更常使用的就是「di
我們在「【🔒 Python 先修班】👆 打造友善的使用者互動CLI介面」有介紹Python的Click命令列參數設計介面的方式, 那我們除了設計出介面提供使用者互動之外, 有時候也需要一點驗證機制, 畢竟我們心裡都清楚「garbage in, garbage out」的後果, 為了減少這種狀
我們在處理音檔時常常會使用到 [soundfile](https://pypi.org/project/soundfile/) 這套工具, 當我們試圖讀取檔案時卻發生了這樣的錯誤訊息… TypeError: Not allowed for existing files (except 'RAW')
關於多執行緒/多行程的使用方式 在Python 3.2版本之後加入了「concurrent.futures」啟動平行任務, 它可以更好的讓我們管理多執行緒/多行程的應用場景,讓我們在面對這種併發問題時可以不必害怕, 用一個非常簡單的方式就能夠處裡, 底下我們將為您展示一段程式碼: imp
這個問題發生在我們開發Python的Websocket Server時, 使用以下的程式碼架設服務 start_server = websockets.serve(server, 'localhost', args.port) async with start_server:
假設我們今天想要訓練一個AI模型, 那麼我們會有一批大型資料集, 通常會根據比例來切分三個模型訓練所需的訓練集(train)、驗證集(dev)、測試集(test), 而我們本次會示範一下Python如何對一個List清單進行切分, 基本上大同小異, 我們只要掌握作法即可概念相通。 任務提示
我們在使用Python語言進行軟體開發時, 常常會需要dict這個資料結構來儲存複雜結構的資料, 就如同JSON一般, 我們會具有這樣的Key/Value模式組成的資料結構, 如下圖: 而當我們在Python的世界裡, 除了嚴謹規範資料欄位的@dataclass之外, 更常使用的就是「di
我們在「【🔒 Python 先修班】👆 打造友善的使用者互動CLI介面」有介紹Python的Click命令列參數設計介面的方式, 那我們除了設計出介面提供使用者互動之外, 有時候也需要一點驗證機制, 畢竟我們心裡都清楚「garbage in, garbage out」的後果, 為了減少這種狀
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
Thumbnail
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await 保留同步。
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
Thumbnail
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await 保留同步。