網路協程程式設計

鄭樹新發表於2016-08-21

 一、背景

為什麼需要網路協程?

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網路通訊與伺服器程式設計框架下載:https://github.com/zhengshuxin/acl

acl網路協程庫URL:https://github.com/zhengshuxin/acl/tree/master/lib_fiber

相關文章