Go 學習筆記 - 協程和 IO 多路複用
1、什麼是協程?
-
進程的虛擬地址空間劃分爲用戶空間和內核空間
-
線程是進程中的執行體, 擁有一個執行入口, 以及從虛擬地址空間中分配的棧 (包括用戶棧和內核棧)
-
操作系統會記錄線程控制信息, 線程在獲得時間片就可以被執行, CPU 的指令指針和棧指針就會記錄和執行線程相關的信息
-
當線程創建了很多個執行體, 並且也給這些執行體指定入口和分配棧內存, 就可以按需調度這些執行體
-
線程爲了控制這些執行體的切換, 也會記錄這些執行體的信息, 如 ID、棧的位置、執行入口、執行現場等數據
-
上述這種由線程創建的執行體就被稱爲協程, 操作系統對協程一無所知, 所以協程也可被稱爲用戶態線程
2、協程間切換
-
當用戶線程獲得 CPU 的時間片後, 可以創建很多協程
-
某個協程讓出執行權時, 會有協程棧保存執行現場等信息, 接着就可以切換到其他協程
-
協程獲得執行權時, 會根據之前協程棧保存的現場信息數據恢復執行
-
協程是由用戶態調度的多任務模型
3、協程和 IO 多路複用
-
高併發成爲爲主流趨勢, 多進程模型請求下內存資源緊張
-
多進程模型下, 內核態和用戶態切換兩頭忙
-
協程這種用戶態調度模型受到了關注
-
協程和 IO 多路複用的結合成爲很好的高併發解決方案
4、IO 多路複用
-
通過操作系統的進程控制信息可以找到進程打開文件描述信息、創建 socket 信息等
-
socket 的操作都由操作系統來完成, 需要用戶程序通過系統調用來完成
-
每創建一個 socket, 就會在打開的文件描述符表中創建一條記錄信息, 返回給用戶程序的是 socket 描述符
-
每個 TCP socket 被創建時, 操作系統都會在內核空間分配度緩衝區、寫緩衝區
-
獲取響應數據時, 需要從讀緩衝區拷貝數據
-
要通過 socket 發送數據時, 先要把數據拷貝到寫緩衝區
-
問題來了:
-
當用戶程序想要從讀緩衝區獲取數據, 不一定有
-
當用戶程序想要發送數據的時候, 寫緩衝區, 不一定有空間
-
解決辦法:
-
阻塞式 IO
-
讓出 CPU 進到等待隊列裏, 等 socket 準備就緒, 再次獲得時間片時就可以繼續執行了
-
要處理一個 socket, 就需要佔用一個線程, 處理完才能繼續下一個
-
高併發場景下, 開銷很大
-
非阻塞式 IO
-
不讓出 CPU, 但需要一直詢問 socket 是否準備就緒
-
但這種忙等待的方式可能會空耗 CPU, 增加響應延遲
-
IO 多路複用
-
操作系統把需要等待的 socket 加入到監聽集合
-
用戶程序就可以通過一次系統調用同時監聽多個 socket
-
Linux 提供三種 IO 多路複用方式:
-
select
-
可以設置要等待的描述符, 也可以設置要等待的超時時間
-
如果有準備好的 fd, 或到了超時時間, select 系統函數就會返回
-
select 系統函數支持可讀、可寫、可執行
-
fd_set 是一個 unsigned long 數組, 16 個元素, 每一位對應一個 fd, 最多可以監聽 1024 個
-
並且每次調用都需要傳遞所有監聽的集合, 需要頻繁的從用戶態到內核態拷貝數據
-
每次都需要遍歷整個監聽集合判斷可操作的 fd
-
poll
-
只能解決最大監聽 fd 描述符的個數, 其他問題依然沒有得到解決
-
epoll
-
epoll 提供三個關鍵接口
-
epoll_create 用於創建 epoll 句柄
-
epoll_ctl 用於添加或刪除 fd 與對應的事件信息, 可以指定要監聽的 fd 和事件類型, 可以傳入額外的 data 數據, 每次只需要傳入一個 fd, 無需傳入所有 fd 集合
-
epoll_wait 返回 fd 集合都是已經準備就緒的, 這樣就不會阻塞和空耗 CPU 了
-
epoll 有什麼問題:
-
比如當某個線程讀 fd 時, 可能讀到一半時, CPU 時間片就到了, 需要讓出執行權, 保存現場數據
-
需要等到下一次時間片的 CPU 執行權時, 才能恢復現場繼續執行
-
解決辦法:
-
頻繁切換線程, 需要保存和恢復執行現場
-
可以交給協程來處理, 對於 CPU 來說同一個線程的多個協程無感知
-
協程擁有自己的棧空間, 用於保存和恢復現場相對比較容易, 和具體的線程業務邏輯解耦了
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/cjEgPNb22LTQlDTgtxWr4A