ASIO 騰空出世 (那些年我們追過的網路庫.PartII)
ASIO 騰空出世
在地球最大的島上,另一位少年開始拜讀 ACE 的大作。那時候,沒有 libuv 沒有 libev 更沒有 libevent . 有的只是 ACE. 然而這個南方小國的少年沒有跟風陷入 ACE 崇拜,他以敏銳的目光察覺到了 ACE 的弊病。 ACE 哪裡做的不好?又哪裡是值得借鑑的?
少年在給 c++ 委員會寫的一篇上書中說,Proactor 模型乃最優模型。而 Proactor 模型,乃 ACE 提出的 6 個模型之一。根據 IT 界贏者通吃律,一個優秀的網路庫,只需要支援 Proactor 模型即可。 支援其他次優模型都是徒勞的。ACE 試圖全盤通吃,犯了大忌。
少年在一次開發者大會的演講上,再次透露,網路庫不宜做成框架,而是要像系統的API那樣,作為一個樂高積木。ACE 做成了一個框架,同樣不妥。
你說了那麼多 ACE 不好,有本事你弄個好的啊? 批評者向來都是這麼理直氣壯。 正如 ACE 的作者實踐了 “紙上得來終覺淺 絕知此事要躬行” 一樣,這位勇敢的少年也拿出了 ASIO, “實踐出真知”,他如是說。
那還是 SARS 病毒肆虐的年代,幾乎沒有人注意到,今後顛覆C++的網路世界的 ASIO 悄然出世了。而他的父親,還只是悉尼的學子。ASIO 並沒有顯赫的家庭背景,然而英雄不問出處,它註定將有不平凡的一生。
俗語有云,三歲看老。在 asio 才三歲的時候,它父親就將 asio 引薦給了 c++ 委員會的老人們。上一次他們這麼做的時候,他們接納了 STL。 ASIO 最終被內定,然後放入 Boost 鍛鍊, 經過 Boost 十餘的鍛鍊,ASIO 終於在 2017 年進入了 c++ 標準。
Proactor ? Why Proactor
在給 c++ 老人會的引薦信裡,asio 爸爸仔細闡述了asio的設計抉擇回答了圍繞 asio 的設計提出的很多問題。 為什麼 Proactor 會是最佳模型?
- 跨平臺 許多作業系統都有非同步API,即便是沒有非同步API的Linux, 通過 epoll 也能模擬 Proactor 模式。
- 支援回撥函式組合 將一系列非同步操作進行組合,封裝成對外的一個非同步呼叫。這個只有Proactor能做到,Reactor 做不到。意味著如果asio使用Reactor模式,就對不起他“庫” 之名。
- 相比 Reactor 可以實現 Zero-copy
- 和執行緒解耦 長時間執行的過程總是有系統非同步完成,應用程式無需為此開啟執行緒
Proactor 也並非全無缺點,缺點就是記憶體佔用比 Reactor 大。Proactor 需要先分配記憶體而後處理IO, 而 Reactor 是先等待 IO 而後分配記憶體。相對的Proactor卻獲得了Zero-copy好處。因為記憶體已經分配好了,因此作業系統可以將接受到的網路資料直接從網路介面拷貝到應用程式記憶體,而無需經過核心中轉。 Proactor 模式需要一個 loop ,這個 loop asio 將其封裝為 io_service.他不僅是 asio的核心,更是一切基於asio設計的程式的核心。
宇宙級非同步核心
io_service 脫胎於 IO 但不僅用於 IO. Christopher Kohlhoff 在給委員會的另一份編號 N3747 的信上上說它是 宇宙級非同步模型 Universal Asynchronous Model。在宇宙級非同步模型裡,一個非同步操作由三部分構成
- 發起 按照 asio 的編碼規範,所有的發起操作都使用 async_ 字首,使用 async_動詞 的形式作為函式名。
- 執行 非同步過程在發起的時候被executor執行(系統可以是支援 AIO 的核心,不支援 AIO 的系統則是 aiso 使用者層模擬)
- 完成並回調 在發起 async_* 操作的時候,總是攜帶一個回撥的閉包。asio使用閉包作為非同步事件完成的處理回撥,沒而不是C式的回撥函式。asio的宇宙非同步模型裡,回撥總是在執行 io_service::run 的執行緒裡執行。asio絕不會在內部執行緒裡呼叫回撥。
在回撥裡發起新的非同步操作,一輪套一輪。整個程式就圍繞著 io_service::run 運轉起來了。 io_service 不僅僅能用於非同步 IO ,還可以用來投遞任意閉包。實現作為執行緒池的功能。這一通用型非同步模型徹底擊敗微軟 PPL 提案,致使微軟轉而研究協程。然而微軟在協程上同樣面臨 asio 的絞殺。
閉包和協程
宇宙級 asio 使用閉包作為回撥,而 C 庫只能使用函式+void*, ACE 雖然使用的 C++語言,卻不知道閉包為何物,使用的是 虛擬函式作為回撥。需要大量的從 ACE 的物件繼承。以閉包為回撥,asio更是支援了一種叫“無棧協程”的強悍武器。 asio的無棧協程,僅僅通過庫的形式,不論是在效能上,還是易用性上,還是簡潔性上,甚至是B格上,都超過了微軟易於修改語言而得的 await提案。
微軟,乃至 ACE ,並不是不知道閉包,而是在c++裡實現閉包的宇宙級executor —— 也就是 io_service,需要對模板技術的精通。 asio “把困難留給自己,把方便帶給大家”,以地球人無法理解的方式硬是在 c++98 上實現了宇宙級非同步核心。 當然,如果 c++11 早點出現,如果 c++17 早點出現,實現 asio 的宇宙模型會更加的簡單 —— 其實這也是 c++ 的理念,增加語言特性,只是為了讓語言用起來更簡單。
buffers
有了閉包的支援,記憶體管理也變得輕輕鬆鬆起來。 ASIO 本身並不管理記憶體,所有的IO操作,只提交對使用者管理的記憶體的引用,稱 Buffers。asio::buffers 引用了使用者提交的記憶體,保持整個 IO 期間,這塊記憶體的有效性是使用者的責任。然而這並不難! 因為回撥是一個閉包。通過閉包持有記憶體,只要 asio 還未回撥,閉包就在,閉包在,記憶體在。asio 在呼叫完回撥後才刪除相應的閉包。因此資源管理的責任可以丟給閉包,而閉包可以通過智慧指標精確的控制記憶體。 不是 GC , 勝於 GC 千百倍!益於c++的 RAII機制,再無記憶體洩漏之憂!
進入 ASIO 的世界
對 C++ 網路庫的歷史也介紹到差不多了,接下來的章節裡,帶你深入理解asio , 讓你同時獲得開發效率和執行效率。 一些在本章裡,可能對許多人來說都是初次見到的技術,將在本書剩餘章節裡詳細介紹。
翻開下一頁,進入 ASIO 的世界,領略 C++ 的博大精深,享受網路遨遊的快感吧!