幾種使用 async programming 的情境:
GUI: 像是Web、iOS、Android apps。在 event-driven 的架構下用 callback 滿直覺的, 但是為了避免卡住 UI thread 而需要連續多個 non-blocking I/O 才能完成一件事時,寫起來就不直覺了。這時新增 thread 用 blocking I/O 寫起來比較直覺,但是不容易處理 thread-safety。
server: 像是 Web server、API server、reverse proxy。要能同時承受上千筆連線,必須用 non-blocking I/O。遇到 CPU 吃重的工作時,可以轉交給其它專門的 server (如向資料庫讀資料或用 Lucene 處理 text search)。
其它像是一些會用到多筆 http request 取資料的小程式,例如用 Gmail API 取出所有信件的附件,或是 stress test 的工具,或是 Web crawler,這些也會用 non-blocking I/O 加速程式 (或用 multi-thread/multi-process + blocking I/O 也行,最近才寫了個小工具 prunner.py)。
共通需求
- 用 non-blocking I/O 避免卡住服務或縮短完成時間。
- 用 single thread 降低實作複雜度。
為了滿足上述需求,主流的解法是用 single-thread event loop。但遇到 CPU bound 時還是得開新 thread。
注意這個共通需求對 Web page 和 server 是必要需求,但對其它情境只是「優先希望的作法」,Android、iOS 或 crawler 還是可以選擇新增 thread/process 使用 blocking I/O,然後自行承擔 multi-thread/multi-process 衍生的問題。
non-blocking I/O 的問題
single-thread event loop 只有幫忙監聽所有 non-blocking I/O 的動靜,用 callback 的方式通知結果。用 callback 處理 GUI 反應很自然,但是需要依序發數個 http request 的時候,在 callback 裡跳來跳去就頭大了,很難看出程式的流程。
換句話說,我們需要以下功能:
- 用 synchronous API 來使用 non-blocking I/O。方便看出控制流程。
- 執行中可以得到 call stack,方便除錯。
提供底層使用 non-blocking I/O 的 synchronous API
如何在不增加新的語法下提供這樣的功能?換句話說,我們還是想新增 thread 用「看起來像 blocking I/O 但底層是 non-blocking I/O」的 API。Python 的 gevent 提供一個不錯的解法:
- 以 event loop 為底,實作 coroutine (由 greenlet 完成)
- 提供 synchronous API + non-blocking I/O 實作 (用 yield 主動讓出執行權)
- task scheduler 會在 I/O 完成後再回到當初 yield 處,讓使用 API 的人用起來像 blocking I/O 一般
值得一提的是,coroutine 是 non-preemptive multitasking,比使用 native thread 的 multi-thread (preemptive multitasking) 容易避免 race condition,不過代價是要小心不要寫出 busy loop 卡住整個 process。
新語法:async/await
相對於 Python 陣營有龐大使用 blocking I/O third-party library 的問題,JavaScript 陣營 (Node.js / Browser) 天生就使用 non-blocking I/O (長年以來只有一個 thread 的自然結果),這點很有優勢。
JavaScript 不用 coroutine 而是發展出兩種方式提供 synchronous API:
- Promise: 看起來像 synchronous API,只是囉嗦了點,還有可能會搞混執行順序。
- async/await: 基於 Promise 定義新的語法,簡化使用並突顯使用 async 的程式。
詳情見The long road to Async/Await in JavaScript。另外 What Is Asynchronous Programming? 和 Managing Asynchronous Code - Callbacks, Promises & Async/Await 也值得一看。
另外,Python 也會在 3.5 加入新語法 async/await,用它們明確地定義 coroutine,藉此區分 generator。async/await 去掉 thread 的概念,應該會比 coroutine 適合。但對習慣 multi-thread 的人來說,可能 coroutine 比較親切吧?
整體來說,未來繼續用 Node.js 或 Python 寫 server 程式還是不錯的選擇,語法簡單、用戶眾多又有廣大的 third-party library 可用 (目前各用 gevent 和 Node.js 寫過一次上線的服務)。
雜談
話說大家都用 synchronous API 提供 non-blocking I/O 後,還能稱這為 async programming 嗎?不過語法裡有 async/await啦。具體來說,async programming 的定義到底是什麼......
沒有留言:
張貼留言