添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
### 參考文件 * [[Medium] aysnc await 學習筆記](https://medium.com/%E9%AB%92%E6%A1%B6%E5%AD%90/aysnc-await-%E6%95%99%E5%AD%B8%E7%AD%86%E8%A8%98-debabdb9db0e) * [IT 邦幫忙] python的asyncio模組 * [(一):異步執行的好處](https://ithelp.ithome.com.tw/articles/10199385) * [(二):異步程式設計基本概念](https://ithelp.ithome.com.tw/articles/10199403) * [(三):建立Event Loop和定義協程](https://ithelp.ithome.com.tw/articles/10199408) * [[Taiker] Speed Up Your Python Program With Concurrency](https://blog.taiker.space/python-speed-up-your-python-program-with-concurrency/) * [Go Concurrency Patterns](https://talks.golang.org/2012/concurrency.slide#7) ## 同步與非同步 一般來說,Python 在對任一行程式進行請求的時候,都等到程式回應之後在進行下一行程式,也就是所謂的 IO 阻塞式的語言。 而非同步的差別在於執行過程不會等待 IO 回應,而是繼續執行下面的程式碼,讓 IO 與後續流程作為**事件 (event)** 形式,並透過**輪詢 (polling)** 與**回調 (callback)** 觸發執行後續程式碼。 最為經典的非同步語言為 JavaScript,也有其他語言透過套件等其他形式去得到非同步的效果,如 ROS,以及本篇著重的對象: Python 的 `asyncio`. > 非同步程式另一個範例是 Non-blocking sockets (select). 同樣也是透過事件、輪詢與回調進行多端點非阻塞式連線。 直白一點講,故事是這樣的: 1. (同步) 「小明到了便當店點了雞腿便當,等到雞腿便當拿到之後再到飲料店點珍奶。」 2. **(非同步)** 「小明到了便當店點了雞腿便當,在等待的過程中先到飲料店點珍奶,之後再看哪邊先做完就先去拿已經做完的餐點。」 基本上這就是兩者最大的不同。 | Type | Illustration | | ------------------------- | ------------------------------------------------------------------------------------------- | | Synchronous 同步 | ![](https://hackmd.mcl.math.ncu.edu.tw/uploads/upload_aa6623c76f9356e90020570f18cd2ba7.png) | | Multi-processing 多處理器 | ![](https://hackmd.mcl.math.ncu.edu.tw/uploads/upload_79f3d9652ee33c33d5a25bd1e00dcfa5.png) | | Multi-threading 多執行緒 | ![](https://hackmd.mcl.math.ncu.edu.tw/uploads/upload_f3038ba07be2728c3150e66e735c755c.png) | | Asynchronous 非同步 | ![](https://hackmd.mcl.math.ncu.edu.tw/uploads/upload_e471e90a794e2cde78b635436762f7fd.png) | > 上表中除第一項,都是並行的可用方法,而挑選適合的方案,重點在於理解自身程式所遇到的瓶頸是 CPU bound (計算瓶頸) 還是 IO bound (等待瓶頸);若前者可考慮套用 Multi-processing 處理,後者可考慮套用 Multi-threading 或是 Async 處理。 > [name=Taiker. [Speed Up Your Python Program With Concurrency](https://blog.taiker.space/python-speed-up-your-python-program-with-concurrency/)] ### 並行 (Concurrent) 與平行 (Parallel) 稍微討論一下這兩個很類似的名詞。並行 (Concurrent) 含意是一種「得以在同時間有兩個以上的計算在處理」的程式語言或是各種演算法;而平行 (Parallel) 是一種實現並行的一種模式。 一個衝突的案例是:當你只有一個處理器時,你依然可以擁有並行運算,但你無法透過平行實現。 > ![](https://hackmd.mcl.math.ncu.edu.tw/uploads/upload_78e809f8404ea64a51416c556b17e9ec.png) > [name=Andrew Gerrand. [Go Concurrency Patterns](https://talks.golang.org/2012/concurrency.slide#7).] ### 非同步的哲學觀:事件與回調 ![](https://hackmd.mcl.math.ncu.edu.tw/uploads/upload_88a6cd6ad01300d9912a63dd2ae2d811.png) > [name=luminousmen. [Asynchronous programming. Cooperative multitasking](https://luminousmen.com/post/asynchronous-programming-cooperative-multitasking)] > 「小明到了便當店點了雞腿便當,在等待的過程中先到飲料店點珍奶,之後再看哪邊先做完就先去拿已經做完的餐點。」 回顧一下上面提到的故事: 1. 「點雞腿便當」跟「點珍奶」就是兩個任務,而指派出去的時候就會進入事件循環 (Event Loop) 2. 二事件接著就會進入 IO 等待階段 (等店家把便當跟珍奶做好),而小明就會在事件循環 (Event Loop) 中一直輪詢 (Pooling) 以查看每個任務的狀態 3. 當有其中一個食物做好的時候 (Callback),就會觸發後續行為 (取餐)。 在 Python 當中會使用 `asyncio` 套件來實現上面的流程,並且會使用 `async` 和 `await` 兩關鍵字去撰寫相關程式碼。 ## 範例程式碼 ### 案例一:`sleep` 我們透過 Python 的 `asyncio` 進行多事件睡眠以模擬 IO bound 狀態下的效果: ```python import time import asyncio start_time = time.perf_counter() # such as time(), but more precise loop = asyncio.get_event_loop() async def pseudo_io(idx): t = time.perf_counter() print(idx, ': send a request at', t - start_time, 'seconds') await asyncio.sleep(1) # do not use time.sleep(1) as its blocking mode t = time.perf_counter() print(idx, ': receive a response at ', t - start_time, 'seconds') if __name__ == '__main__': tasks = [] for i in range(10): task = loop.create_task(pseudo_io(i + 1)) tasks.append(task) loop.run_until_complete(asyncio.wait(tasks)) 你會得到類似以下的結果: 1 : send a request at 0.0005315999999999932 seconds 2 : send a request at 0.0006520999999999888 seconds 3 : send a request at 0.0009392999999999901 seconds 4 : send a request at 0.0010235999999999995 seconds 5 : send a request at 0.0011018 seconds 6 : send a request at 0.0011733999999999911 seconds 7 : send a request at 0.001246199999999989 seconds 8 : send a request at 0.0013195999999999902 seconds 9 : send a request at 0.0013951999999999992 seconds 10 : send a request at 0.0014667999999999903 seconds 1 : receive a response at 1.0014887 seconds 3 : receive a response at 1.0016416 seconds 7 : receive a response at 1.0019822 seconds 10 : receive a response at 1.0023405 seconds 9 : receive a response at 1.0026532 seconds 6 : receive a response at 1.0029613 seconds 8 : receive a response at 1.0087264 seconds 5 : receive a response at 1.0091539999999999 seconds 2 : receive a response at 1.0094932 seconds 4 : receive a response at 1.0099035 seconds ### 案例二:`requests` 網頁請求是一個經典的 IO bound 問題,他也很適合透過非同步手法處理之。 ```python import time import requests import asyncio start_time = time.perf_counter() loop = asyncio.get_event_loop() async def send_request(idx, url): t = time.perf_counter() print(idx, ': send a request at', t - start_time, 'seconds') _ = await loop.run_in_executor(None, requests.get, url) t = time.perf_counter() print(idx, ': receive a response at ', t - start_time, 'seconds') if __name__ == '__main__': tasks = [] url = 'https://www.google.com' for i in range(10): task = loop.create_task(send_request(i + 1, url)) tasks.append(task) loop.run_until_complete(asyncio.wait(tasks)) 類似結果如下: 1 : send a request at 0.0 seconds 2 : send a request at 0.002009868621826172 seconds 3 : send a request at 0.002009868621826172 seconds 4 : send a request at 0.003000020980834961 seconds 5 : send a request at 0.003000020980834961 seconds 6 : send a request at 0.003000020980834961 seconds 7 : send a request at 0.004000186920166016 seconds 8 : send a request at 0.0060007572174072266 seconds 9 : send a request at 0.00900578498840332 seconds 10 : send a request at 0.012999773025512695 seconds 1 : receive a response at 0.08250999450683594 seconds 5 : receive a response at 0.0855417251586914 seconds 8 : receive a response at 0.08650994300842285 seconds 6 : receive a response at 0.08753800392150879 seconds 7 : receive a response at 0.08850979804992676 seconds 10 : receive a response at 0.08951115608215332 seconds 2 : receive a response at 0.34174418449401855 seconds 4 : receive a response at 0.34679532051086426 seconds 9 : receive a response at 0.3497951030731201 seconds 3 : receive a response at 0.35082435607910156 seconds