以 Sudoku Solver 為例,展示並發網絡服務的多種架構演進,重點介紹 Muduo 庫內建的兩種多線程模式:Thread Pool(方案 8) 與 Multiple Reactors / One Loop Per Thread(方案 9)。
\r\n 分隔,TCP 長連接。[id:]<81 digits>\r\n(id 選擇性,支援 Pipeline)。[id:]<81 digits>\r\n 或 [id:]NoSolution\r\n。核心邏輯在 onMessage 解析協議、呼叫 solveSudoku(純函式、線程安全),再 send 回覆。完整代碼見 server_basic.cc。單線程無法利用多核。
參考 UNP CSDA 與陳碩總結,關鍵指標:
fork()+exit():160 µspthread_create+join:12 µs| 方案 | 模型 | 優點 | 缺點 |
|---|---|---|---|
| 0 | Iterative | 簡單 | 一次只服務一個客戶 |
| 1 | Process per Connection | 隔離性好 | fork 開銷大,不適合短連接 |
| 2 | Thread per Connection | 開銷較小 | 線程數受限,數千連接壓垮調度器 |
| 5 | 單線程 Reactor | IO 密集型極佳 | CPU 密集型無法發揮多核 |
| 6 | 每請求一線程 | 利用多核 | 頻繁建線程,Out-of-order |
| 7 | 每連接一計算線程 | 保序 | 連接數受限於線程數 |
| 8 | Reactor + Thread Pool | IO/計算分離,固定線程池 | 兩次上下文切換,Out-of-order |
| 9 | Multiple Reactors | One loop per thread,減少切換,保序 | 單連接突發計算受限單核 |
| 10 | 混合模式 | 兼顧 IO 與計算突發 | 實現較複雜 |
主 Reactor 負責 IO,計算任務丟給 ThreadPool。關鍵差異:
// server_threadpool.cc 片段
threadPool_.run(boost::bind(solve, conn, request, id)); // 交給線程池
static void solve(const TcpConnectionPtr& conn, const string& req, const string& id) {
string result = solveSudoku(req);
conn->send(id.empty() ? result+"\r\n" : id+":"+result+"\r\n");
}
注意:此模式可能 Out-of-order,客戶端需靠 id 匹配。
一個 mainReactor 负責 accept,採 Round-robin 分派連接到多個 subReactor(各自一線程)。連接生命周期完全在該線程,天然保序、無額外切換。
代碼差異極小:
server_.setThreadNum(numThreads); // 單行啟用多線程
diff server_basic.cc server_multiloop.cc 僅增加構造函數參數與該行。
在方案 9 基礎上再加 server_.setThreadNum(numThreads) 並保留 ThreadPool,適合同時有 IO 突發與計算突發的場景。
推薦模式:Event Loop Per Thread + Thread Pool。Muduo 將這一思想封裝為開箱即用的 TcpServer::setThreadNum(),讓開發者專注業務邏輯。