網絡協程編程

一、背景

 爲什麼需要網絡協程?

1、協程/纖程並不是一個新概念
2、大併發、高性能對於服務端的高要求
3、移動設備的快速增長加大了服務端大併發壓力
4、Go 語言的興起將協程帶到了一個新的高度

支持協程的編程語言:
1、Go 語言,非常容易支持大併發、高性能
2、Python 語言
3、Erlang 語言
4、Lua 語言
。。。。。。

爲什麼要設計一套 C/C++ 網絡協程庫?
1、學習一部門語言的成本要遠高於學習一個庫
2、C/C++ 程序員多年的經驗積累損耗巨大
3、C/C++ 綜合運行效率高

二、關於併發

 - 雖已進入多核時代,但服務器的 CPU 核心總是有限的
 - 當進程/線程數越多操作系統的調度算法就越低效
 - TCP長連接及連接池的存在,造成服務端80%以上的連接是空閒的

爲支持併發,我們需要採用:
1、多進程模式:支持併發能力非常有限,如 Postfix,Xinetd;
2、多線程模式:比多進程模式有提高,但依然有限,如 Mysql;
3、非阻塞模式:性能高,但編程複雜度極高,如 Nginx,Redis;
4、基於事件的多線程模式:併發度有較大提高,但編程提升依然有限,如 acl 中的 master_threads 服務模式;

三、設計目標

 我們需要一種新的編程模式來滿足C/C++程序員:
1、支持大併發、高性能,較低的資源使用率
2、較低的編程複雜度:順序思維模式
3、適合多數應用場景,提供豐富且簡單易用的接口
4、與第三方網絡庫無縫集成,無需修改第三方庫

四、一個簡單的協程示例

1、創建協程類似於創建線程
2、支持大併發、高性能
3、順序性編程方式
4、無需更改第三方庫
5、僅使用一個線程資源

 五、協程的調度方式

 1、上下文切換
 通過操作系統提供的 API 完成:getcontext、makecontext、swapcontext、setcontext;
 或 自己通過彙編語言來實現協程運行棧空間的切換
 實現庫舉例:libtask,boost,libgo, libco,coroutine  等

 2、信號跳轉
 通過系統提供的 API 完成:siglongjmp、longjmp、setjmp、sigsetjmp 等
 實現庫舉例:libmill,st ,coroutine 等

 

六、協程切換方式


 

七、網絡協程調度


 

1、IO事件協程監控所有的IO事件
2、網絡協程運行時遇到IO阻塞,則被掛起,其IO句柄由IO事件協程監控
3、IO事件發生時,其綁定的協程被再次喚醒

 

八、如何與第三方庫無縫集成

1、HOOK IO相關API
讀 API:read/readv/recv/recvfrom/recvmsg
寫 API:write/writev/send/sendto/sendmsg
其它 API:pipe/popen/pclose/open/close/fcntl
2、HOOK 網絡相關API
socket/socketpair/bind/listen/accept/connect
poll/select/epoll_create/epoll_wait/epoll_ctl
gethostbyname/gethostbyname_r

通過 HOOK 系統底層 API,可以實現:
1、直接接管第三方庫(如:mysql/http/redis 等庫)的網絡連接及通信過程
2、直接接管第三方庫的域名解析過程
3、將第三方網絡阻塞過程協程化,在協程庫底層轉化爲非阻塞過程

 

將mysql庫協程化的例子參見:acl/lib_fiber/samples/mysql

 

九、爲何要 HOOK 很多系統API

1、poll/select 爲網絡編程中常用系統 API
2、很多第三方網絡庫用 poll/select 模擬IO超時
3、epoll 在 reactor 類應用(如:聊天)方面比較廣泛
4、gethostbyname 在域名解析方面應用廣泛
5、listen 需要將監聽描述字設爲非阻塞模式
6、connect 需要將連接描述字設爲非阻塞模式
7、bind/socket/socketpair/。。。爲便於將出錯號與協程綁定
 

十、基於協程的 errno

因爲每個線程中存在大量協程,當某個協程的IO過程出錯時,如果實現不同協程之間的 errno 是相互隔離的?
--- 在 Linux 平臺下直接 HOOK __errno_location 系統函數
參見:/usr/include/bits/errno.h

extern int *__errno_location (void) __THROW __attribute__ ((__const__));
#define errno (*__errno_location ())

針對進程內全局變量:errno,操作系統將該變量定義爲一個函數指針地址,函數內部會通過線程局部變量方式給每一個線程分配一個 error 對象
因此,通過 hook __errno_location 函數,在協程庫裏給每個協程一個協程局部變量,實現了 errno 全局變量的協程安全性

 

十一、內存安全檢測

配合 valgrind 做內存檢測:
- valgrind 與 xxxcontext 的不兼容性
- 需下載 valgrind 開發包,調用 VALGRIND_STACK_REGISTER通知
  valgrind 跳過檢測該內存區域
- 檢測時在 Makefile 裏打開 –DUSE_VALGRIND 編譯選項,重新編譯 lib_fiber.a

 

十二、有效使用多核

 每個線程一個獨立的協程調度器,通過創建多個線程使用多核
使用 acl master 服務器框架,創建多進程使用多核,每個進程一個協程調度器

多線程示例參見:acl/lib_fiber/samples/redis_threads
多進程示例參見:acl/lib_fiber/samples/master_fiber

 

十三、協程同步原語



 

基於協程的協程鎖:
1、協程互斥鎖
2、協程讀寫鎖

 

十四、協程掛起與喚醒

-- 協程掛起方式
1、主動讓出 CPU 控制權
當前運行的協程通過調用 acl_fiber_yield 主動讓出 CPU 控制權,協程調度器調用別的協程
2、指定休眠時間
當前運行的協程通過調用 acl_fiber_sleep 使當前協程休眠指定時間
3、IO阻塞被掛起
當前運行的協程等待IO完成時,需要將自身掛起

-- 協程喚醒方式
1、主動 yield 的協程又重新獲得 CPU 控制權
2、處於休眠狀態的協程時間到達
3、因IO阻塞而被掛起的協程因IO準備好而被喚醒

示例參考:
1、yield 方式:acl/lib_fiber/samples/fiber
2、sleep 方式:acl/lib_fiber/samples/sleep
3、IO 方式:acl/lib_fiber/samples/select

 

十五、過載保護


 

十六、協程間通信

協程間爲什麼需要通信?
1、業務邏輯的模塊化
2、業務模塊的分層設計
3、團隊開發的協作性

協程間“通信”的本質:
- 協程間數據的傳遞通過協程上下文的切換,本質上是協程間的數據交換

協程間“通信”的成本:
1、協程上下文切換
2、內存分配、釋放
3、數據拷貝

協程間“通信”方式:
- 支持多對多數據交互



- 協程通信管道支持多對多方式
- 協程間通信通過切換協程上下文及數據交換完成
- 協程間通信時的數據交換支持緩衝模式
- 協程間通信時的數據交換採用隨機分配方式
 

十七、線程間通信

協程模式下爲何需要線程間通信?
- 爲使用多核,開啓多個線程,線程間需要交換數據
- 有些任務需要在線程池裏異步完成,結果需要傳遞給主線程

協程模式下線程間的通信方式:
- 無鎖消息隊列 + IO 模式

 

十八、線程間通信


1、生產者/消費者之間優先通過無鎖隊列進行數據傳遞
2、當生產者無數據時,消費者通過IO堵塞
3、當消費者堵塞在IO等待新消息時,生產者若有新消息則通過IO通知消費者
4、無鎖隊列利用率越高,則處理性能越高

 

十九、應用場景

(一)、問答式應用服務
基於 HTTP 協議的服務應用,諸如:網站
基於 SMTP/POP3/IMAP 協議的服務應用
(二)、生產者 – 消費者類應用服務
如消息隊列類應用
(三)、reactor 和 proactor 兩種模式的結合
統一的事件引擎監控所有的網絡連接,有一個連接就緒時創建協程獨立處理
此類應用如聊天服務、遊戲服務等無狀態的應用服務
(四)、大併發類應用服務
因爲通過協程方式,將上層應用的堵塞式在底層轉爲非阻塞模式,所以非常容易以較低資源支持大併發類應用
如內網的多數應用服務爲提高效率都支持連接池模式,需要服務端支持非常大的併發
(五)、網絡限流
在協程中可以直接 sleep,非常容易控制網絡流量

 

二十、協程編程注意事項

(一)、協程運行堆棧空間的合理分配
每個協程都需要分配一定的內存空間用於上下文的切換,如果分配大了則會造成內存浪費,分配小了可能造成意外不可恢復的崩潰
一般情況下,每個協程分配32KB ~ 320KB

(二)、協程間需要協作,防止有的忙死,有的餓死
當協程長期佔用 CPU 時,應該主動 yield 讓出 CPU

(三)、協程內防止有堵塞式操作,以防堵塞當前線程中的所有協程
應通過對業務邏輯模塊進行分類,確定不同的協程工作方式,使堵塞操作放在線程池中運行

 

二十一、參考

 

 基於協程的簡單網絡服務:使用 acl 協程編寫高併發網絡服務

 基於協程的 WEB 服務:使用協程方式編寫高併發的 WEB 服務

 協程庫:https://github.com/acl-dev/acl/tree/master/lib_fiber

 acl svn:svn checkout svn://svn.code.sf.net/p/acl/code/trunk acl-code

 acl github:https://github.com/acl-dev/acl

 acl 國內鏡像:http://git.oschina.net/acl-dev/acl/

 qq 羣242722074

 微博:http://weibo.com/zsxxsz/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章