libco 是騰訊開源的一個協程庫,主要應用於微信後臺RPC框架,下面咱們從爲何使用協程、如何實現協程、libco使用等方面瞭解協程和libco。程序員
why協程後端
爲何使用協程,咱們先從server框架的實現提及,對於client-server的架構,server最簡單的實現:微信
while(1) {accept();recv();do();send();}多線程
串行地接收鏈接、讀取請求、處理、應答,該實現弊端顯而易見,server同一時間只能爲一個客戶端服務。架構
爲充分利用好多核cpu進行任務處理,咱們有了多進程/多線程的server框架,這也是server最經常使用的實現方式:框架
accept進程 - n個epoll進程 - n個worker進程異步
以上框架以事件監聽、進程池的方式,解決了多任務處理問題,但咱們還能夠對其做進一步的優化。socket
進程/線程是Linux內核最小的調度單位,一個進程在進行io操做時 (常見於分佈式系統中RPC遠程調用),其所在的cpu也處於iowait狀態。直到後端svr返回,或者該進程的時間片用完、進程被切換到就緒態。是否能夠把本來用於iowait的cpu時間片利用起來,發生io操做時讓cpu處理新的請求,以提升單核cpu的使用率?分佈式
協程在用戶態下完成切換,由程序員完成調度,結合對socket類/io操做類函數掛鉤子、添加事件監聽,爲以上問題提供瞭解決方法。函數
用戶態下上下文切換
Linux提供了接口用於用戶態下保存進程上下文信息,這也是實現協程的基礎:
以上函數與保存上下文的 ucontext_t 結構都在 ucontext.h 中定義,ucontext_t 結構中,咱們主要關心兩個字段:
stack_t 結構用於保存協程數據,該空間須要事先分配,咱們主要關注該結構中的如下兩個字段:
獲取進程上下文並切換的方法,總結有如下幾步:
Socket族函數/io異步處理
當進程使用socket族函數 (connect/send/recv等)、io函數 (read/write等),咱們使用協程切換任務前,需對相應的fd設置監聽事件,以便io完成後原有邏輯繼續執行。
對io函數,咱們能夠事先設置鉤子,在真正調用接口前,對相應fd設置事件監聽。一樣,Linux爲咱們設置鉤子提供了接口,以read()函數爲例:
當在prog程序中調用 read() 時,使用的就是咱們實現的 read() 函數。
對於glibc函數設置鉤子的方法,可參考:Let's Hook a Librarg Function
libco
有了以上準備工做,咱們能夠構建這樣的server框架:
accept進程 - epoll進程(n個epoll協程) - n個worker進程(每一個worker進程n個worker協程)
該框架下,接收請求、業務邏輯處理、應答均可以看作單獨的任務,相應的epoll、worker協程事先分配,服務流程以下:
libco 提供瞭如下接口:
socket族函數(socket/connect/sendto/recv/recvfrom等)、io函數(read/write) 在libco的co_hook_sys_call.cpp中已經重寫,以read爲例:
ssize_t read( int fd, void *buf, size_t nbyte ) { struct pollfd pf = { 0 }; pf.fd = fd; pf.events = ( POLLIN | POLLERR | POLLHUP ); int pollret = poll( &pf,1,timeout ); /*對相應fd設置監聽事件*/ ssize_t readret = g_sys_read_func( fd,(char*)buf ,nbyte ); /*真正調用read()*/ return readret; }
小結
由最簡單的單任務處理,到多進程/多線程(並行),再到協程(異步),server在不斷地往極致方向優化,以更好地利用硬件性能的提高(多核cpu的出現、單核cpu性能不斷提高)。
對程序員而言,可時常檢視本身的程序,是否作好並行與異步,在硬件性能提高時,程序服務能力可不能夠有相應比例的提高。