C++协程
產生背景
對于后臺開發我們一個重要問題即使用盡可能少的服務器資源處理海量的請求,除了我們再架構上做多機自動擴容外,我們還必須盡可能提高單機硬件的利用率(CPU利用率+IO利用率)
為了提高硬件的利用率往往我們采用三種技術路徑:
- 多線程
- 異步IO
- 協程
協程和異步IO以及多線程的對比
多線程
多線程可以充分利用CPU的多核,實現真正的并行,它是操作系統的基礎設施。但是線程是一個粗粒度、相對比較笨重的多任務的抽象機制,例如以下幾個特點
- 創建線程非常耗時
- 線程上下文切換非常耗時(要從用戶態切換到內核態;保存當前線程執行環境;加載目標線程的執行環境;再從內核態切換回來等一系列操作)
- 每個線程會預先分配一個幾M的調用棧
- 系統中最多只能同時運行數千個線程(當就緒線程個數遠大于CPU核數,操作系統大量的時間就會用來進行線程切換,性能急劇下降)
異步IO
操作系統中有兩種IO:同步IO和異步IO(5種I/O模型)
同步IO:發起IO(比如read)后,CPU必須原地等待其結束,然后才能繼續往下執行
異步IO:發起IO(比如read)前,會先注冊一個回調函數;發起IO后,CPU無需原地等待其完成,而是繼續往下執行;CPU收到IO完成的中斷信號后,將調用回調函數處理IO完成后的剩下的工作
即:同步IO的邏輯直觀,但是性能較低,僅適用少量IO;異步IO性能好,但是邏輯復雜,適用于大規模并發IO
ps:CPU執行速度是遠大于IO響應速度的,若執行同步IO,到達某一個函數(read執行IO操作),這個函數執行5分種,那么當前線程就必須原地等待5分鐘(忙等待),才能執行后續代碼,這對于CPU資源十分浪費。但是在異步IO模式下,CPU和IO可以全速并行執行,使用率大大提高
協程
多線程和異步IO的缺點即是協程的優點
- 協程的特點在于是一個線程執行,最大的優勢就是協程極高的執行效率。因為創建或切換協程不是線程創建/切換,它相當于一次函數調用,由程序自身控制,和多線程比,線程數量越多,協程的性能優勢就越明顯。
- 協程不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量沖突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。
- 每個協程通常只需要幾十個字節保存相關狀態信息,空間開銷遠低于線程棧;
- 系統種可同時運行數千萬個協程,數量主要取決于可用內存大小(當然cpu核數也限制)
- 協程在性能與異步IO同樣,但是在邏輯上和同步IO一樣直觀
ps:協程不是進程或線程,其執行過程更類似于子例程,或者說不帶返回值的函數調用。我們知道多個線程相對獨立,有自己的上下文,切換受系統控制;而協程也相對獨立,有自己的上下文,但是其切換由自己控制,由當前協程切換到其他協程由當前協程來控制。
處理IO密集型任務
對于大多數后臺程序面臨的都是IO密集型任務,它的處理流程大致如下:
用以上三種方式怎么實現這種架構
| 架構 | 描述 |
| 多線程架構 | 偵聽線程收到一個客戶端請求之后創建一個工作線程(或從線程池種申請一個空閑的線程)處理該請求,工作線程發起同步IO后,原地等待其完成;然后向客戶端返回請求 |
| 異步IO架構 | 工作線程從隊列中取出一個請求之后,注冊一個回調函數并發起一個異步IO;然后立刻返回,周而復始的處理下一個請求;IO完成后,回調函數負責向客戶端返回結果 |
| 協程架構 | 工作線程從隊列中取出一個請求之后,調用一個處理協程;然后立即返回,周而復始的處理下一個請求;處理協程負責發起IO;IO完成后,發起IO的處理協程向客戶端返回請求結果 |
三種架構的對比
| 架構 | 特點 |
| 多線程 | 邏輯直觀、容易實現,但不能充分利用后臺服務器硬件資源,容易發生雪崩 |
| 異步IO | 邏輯雜亂、維護麻煩,但性能卓越,目前依舊是后臺服務器的主流架構 |
| 協程 | 性能卓越且邏輯直觀,是后臺服務器架構的演進方向 |
協程的實現機制
協程和線程的實現很相似,每個線程都有一個對應的線程函數,每個協程也有一個對應的協程函數;線程函數和普通函數沒有區別,但是協程函數和普通函數有區別。
普通函數:每次調用只能從第一條語句開始執行
協程函數:協程函數交出控制權后,可以再次從交出控制權的下一語句開始執行(類比多線程的調度方式)
ps:普通函數每次執行都會從入口進入,當A函數調用B函數,那么只有當B函數執行完畢才能再去執行A剩余的代碼,函數做不到當B執行一半,再去調用A可以從A剩余部分往下執行(每次調用函數都會從函數入口重新開始)。但是協程就可以做到,這種方式和線程十分相似。因為調用普通函數時,調用方的返回地址、入口參數等信息都保存在棧上。函數返回后,棧上的信息會自動清除,所以每次調用普通函數都只能從第一條語句開始
?
協程函數特點
ps:上面所描述的是協程實現的一般原理,根據實現方式不同,可分為有棧協程和無棧協程
有棧協程
技術路線:一個線程可以創建大量協程,每個協程都會保留一個私有棧,協程一旦執行到異步IO處,就會主動交出控制權。同一線程的所有協程以串行方式協作執行,沒有數據爭用的問題
有棧協程的特點
ps1:為了減少有棧協程的空間開銷,有些協程框架用一個共享棧代替每個協程的私有棧。共享棧雖然降低了協程的空間開銷,但卻引進了棧拷貝的時間開銷。
ps2:對稱協程調度邏輯復雜,應用的場景有限,非對稱協程是有棧協程的主流
有棧協程可通過操作系統提供的系統調用實現
| OS | 系統調用 |
| Linux | getcontext,setcontext,makecontext,swapcontext |
| Windows | CreateFiber,ConvertFiberToThread,SwitchToFiber |
無棧協程
技術路線:將異步IO封裝到協程函數中,協程函數發起異步IO后,立即保存執行環境,然后吧控制權交給調用方(Caller),調用方繼續往下執行;異步IO完成后,負責處理IO完成事件的回調函數獲得控制權,回調函數再把控制權轉交給發起IO的協程,發起IO的協程首先恢復執行環境,然后從上一次交出控制權的下一條語句繼續往下執行
無棧協程特點
ps:通過操作系統原子操作或者各種鎖機制可以解決數據爭用問題?
?
有棧協程和無棧協程對比
- 有棧協程的最大缺陷是保存調用棧的開銷太大
- 無棧協程不但具有有棧協程的所有優點,而且空間開銷極低;唯一不足就是需要語言標準和編譯器支持
?
鏈接:https://blog.csdn.net/Eunice_fan1207/article/details/91894192
總結
- 上一篇: nginx 源码调试
- 下一篇: C/C++协程实现-学习笔记