再見 異步回調, 再見 Async Await, 10 萬 個 協程 的 時代 來 了


有關 協程 原理,  見 《協程 和 async await》   https://www.cnblogs.com/KSongKing/p/10799875.html  ,

 

協程 切換 的時間很快,   就是 保存 3 個 寄存器 的 值, 再 修改 3 個 寄存器 的 值,

這  3 個 寄存器 分別 保存的 是  協程 的 棧頂 棧底 指令計數器,

 

切換 協程 就是  保存 上一個 協程 的  棧頂 棧底 指令計數器 ,   再 把 寄存器 的 值 修改 為 下一個 協程 的 棧頂 棧底 指令計數器  。

 

所以,  協程 切換 相當於 是 調用一個 很短 的  方法,  時間復雜度 可以認為是  O(1)    。

 

我在  《協程 和 async await》   中 提到, 協程 避免 閉包 共享變量 帶來的 二次尋址 的 性能消耗,

 

但是,實際上,    有了 協程  的 話,    已經不需要 異步回調 來   減少 線程數量 和 線程切換時間,  當然 也不需要  async await  。

 

我們可以 正常 的 編寫 同步調用 的 方法,比如,  讀取文件, 可以調用 FileStream 的 Read()   方法,  而 不需要 調用 ReadAsync() 或者 BeginRead() 方法 。

 

當然,這個 Read()  方法 需要用  協程 重新實現,   Read()  方法 的 同步等待 要用 協程 的 同步等待 來實現,而不是 線程 的 同步等待 。

 

協程 的 同步等待 和 線程 一樣,   在 Read() 方法 里,   當 調用 操作系統 的 異步 IO   API 時,   當前 協程 掛起,  當前 線程 轉而 執行其它 協程 ,

當 異步 IO 完成時,會把  調用這個 IO 的 掛起 的 協程 喚醒(放入 就緒隊列),這樣 接下來 很快就會 執行到 這個 協程  了  。

 

這和 線程 是一樣的  。    但 我就不知道 操作系統 到底 做了些什么,   以至於 線程 這個 “資源”  如此 “昂貴”,  搞得 這些年 流行 炒作 線程,   像  炒股 一樣 炒 的 很熱,

什么    異步回調 流, libuv, IOCP , ePoll ,  async await ,  狀態機,  編譯器 黑魔法,   GoRoutine,  CoRoutine     ……

Go  語言 的  介紹    每次都 強調 “支持高並發”, “編寫服務器端程序”  ,       我原來還奇怪,  高並發 這個不是 很平常 么,  C# 不也是 高並發 沒毛病  ……

 

這幾天 才 想起來,  原來  Go   強調 “支持 高並發”   大概 是  因為 有  GoRoutine  啊   ~~  !

 

這些 基於 異步回調  和  語法糖   的 做法,   將 代碼  切割 的 支離破碎,    語法糖  篡改了  原始代碼 ,  讓 編譯器 變得 笨重復雜,  讓  程序員 的 代碼 和 編譯器 的 代碼 變得 難以理解 ,      讓 編譯器 技術    升維  為   大型 軟件工程   。

 

我在 《協程 和 async await》  中 提到,  協程 需要 編譯器 在 堆 里 模擬 線程 的  棧 和 上下文 保存區,   其實還需要 模擬一樣,  就是 操作系統(CPU) 的   時間片 調度,        這可有點難,  弄不好 性能 就 一落千尺,  與 我們 輕量 高性能 的 目標 背道而馳  。

 

但是,  實際上,  協程 基本上 不需要 時間片 調度,    只需要 不停執行 就可以,   只有 遇到 IO 的 時候 才 掛起, 執行下一個 協程,  就可以了   。

 

這樣就 差不多 了  。

 

在 協程 架構下,  對於 每一個 請求 ,  可以 創建一個 協程 來處理,處理完了 協程 就 銷毀,  可以 存在 大量  協程  ,

這 體現 出 與 線程 的 區別 是,   不需要 考慮  創建開銷,  不需要 考慮  切換開銷,  不需要 考慮(協程) 數量  。

 

在 一台 服務器 上,    同時 運行  10 萬 個 協程 也可以   。

 

這樣就可以和        異步回調  ,   Async  Await        說 再見 了     。

 

這樣 就可以  “回歸最柔軟的初心, 詩意的棲居在大地上”    ,     什么是   “柔軟的初心”     ?

 

就是  80 年代 的 蘋果電腦 、 中華學習機 、 286 、 Basic 、  90 年代  的  譚浩強 爺爺 的 C 語言   。

還有  70 年代 的 Unix  、  貝爾實驗室  、   網絡鏈路 和 網路協議   。

 

協程   是   ILBC / D#    的  一大 賣點    。

 

大家可能 擔心  對 協程 的 就緒隊列 和 掛起隊列 的 隊列 讀寫 需要 Lock(同步互斥),  這個 Lock  的 性能 怎么樣,

我之前在   ILBC / D#     中 提到過   ILBC  用 CAS 指令 實現  IL Lock ,  用於  堆 管理(讀寫 堆  表),

這個做法 和 C# Java 的 new 操作  讀寫堆表 時 用的 做法 應該是一樣的(我推測)  。

CAS  實現 的 lock 是 很快 的,  可以認為 是 指令級 的 ,   可以認為 時間復雜度 是 O(1)  。

事實上,  測試  C#  的 new 操作 的 性能 大約 是  操作系統 lock (lock 關鍵字 / Monitor.Enter()) 的  10  倍  。

或者說,  C#  的 new 操作 的 時間花費 大約 是  操作系統 lock (lock 關鍵字 / Monitor.Enter()) 的  1/10    。

 

在  “異步回調”  和  async await 語法糖  上  糾纏下去 像是一個 泥塘 ,   新一代 的 架構 是 重新 整理 線程 這個 東西 ,     在 語言 級別 實現 語言級 的 “線程”(協程) 是一個 最好 的 平衡點    。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM