協程(Coroutine)是什麼?
協程就是用户態的線程。
這樣解釋可能過於抽象,讓我們先來回顧一下,另外2個更常見的概念,進程(Process)與線程(Thread)。
「進程是操作系統分配資源的基本單位」,只有在進程內才可以進行內存分配釋放、文件讀寫、網卡數據的接收與發送等的資源操作。
「線程是操作系統調度的基本單位」。
進程和線程的狀態對應用程序透明,並且在內核態中完成調度。
協程對應用程序來説是有狀態的,需要應用程序自行在用户態中完成協程的調度。
協程解決什麼問題?
其實協程這個概念很早就被提出來了,到了互聯網高速發展的階段,才被重視起來。
互聯網上但凡熱門的應用,都至少有成百萬、千萬甚至是上億的用户在使用,服務端同一時間需要處理大量用户的請求。
服務端需要具備處理高併發請求的能力,快速響應用户的請求。
互聯網的服務端基本上執行都是「IO密集型的任務」,而傳統的多進程、多線程併發模型並不能高效的利用cpu,它們在遇到IO阻塞的時候,當前的進程或者線程就會被掛起,併發處理請求的能力有限。
雖然可以使用「IO多路複用 + Reactor模型」來實現高併發,但是這種方案下,業務代碼中充斥着很多的「異步回調函數」,開發人員「心智負擔很重,代碼很難維護」。
引入協程後可以很好的解決這個問題,「IO阻塞時當前協程自動讓出執行權,等IO就緒時再恢復之前被掛起協程的執行」。這樣就可以在業務層採用「同步編碼」,而最後是「異步執行」的效果。在降低心智負擔的同時,也能提供高性能的服務。
協程該如何使用?
一個協程庫的實現,至少需要提供3個API,它們分別是:協程創建(CoroutineCreate)、協程喚醒(CoroutineResume),協程讓出(CoroutineYield)。
為了更好的讓大家掌握協程的概念,我自己使用C++11實現了一個協程庫,並把它開源在github上,地址鏈接為:https://github.com/wanmuc/MyCoroutine
現在讓我們來看看,該如何使用上面的協程庫,在協程中打印出”hello world“,示例代碼如下所示。
#include "mycoroutine.h"
#include <iostream>
using namespace std;
using namespace MyCoroutine;
void HelloWorld(Schedule &schedule) {
cout << "hello ";
schedule.CoroutineYield();
cout << "world" << endl;
}
int main() {
// 創建一個協程調度對象,並自動生成大小為1024的協程池
Schedule schedule(1024);
// 創建一個從協程,並手動調度
int32_t cid = schedule.CoroutineCreate(HelloWorld, ref(schedule));
schedule.CoroutineResume(cid);
schedule.CoroutineResume(cid);
return 0;
}
在上述代碼中,我們先創建了一個協程調度對象schedule,它會自動創建對應協程池。Schedule類的成員函數CoroutineCreate用於創建協程,成員函數CoroutineResume用於恢復協程的執行,成員函數CoroutineYield用於協程讓出執行權。
在main函數中,創建完協程之後,調用CoroutineResume來啓動協程的執行,協程的啓動HelloWorld被執行,打印完”hello “之後,協程主動調用CoroutineYield函數,讓出執行權。
進程回到main函數中執行,再次調用CoroutineResume函數,恢復協程的執行,協程從上一次被中斷的地方繼續執行,打印出"world",然後協程執行完畢並退出。
協程退出之後,進程回到main函數中繼續執行,main執行return語句,整個進程退出執行。
協程本質上是在一個進程中,「創建多個調用棧幀,並在不同的調用棧幀之間切換的執行」。在上面的例子中,main函數和HelloWorld函數就是兩個獨立的調用棧幀。
如果還不能很好理解整個調度過程,可以參考下圖。
本文為大廠後端技術專家萬木春原創文章。作者更多技術乾貨,見下方的書籍。