Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C 客戶端執行緒池優化

問題摘要

原始的 C 客戶端實作在使用高並發(50+ 連線)時展現出異常高的 P99 延遲峰值,最大延遲達到 30-40ms,儘管使用相同的 libcurl 函式庫,性能卻明顯比 C++ 甚至 Python 客戶端還差。

根本原因分析

原始實作的問題

原始 C 客戶端為每個並發連線建立一個 pthread:

  • 100 個連線 = 100 個系統執行緒
  • 每個執行緒獨立運行,處理分配給它的訂單
  • 這造成了大量的上下文切換開銷
  • 系統排程器在執行緒管理上遇到困難
  • 某些執行緒經歷排程延遲,導致延遲峰值

性能影響

使用原始實作的測試結果(5000 個訂單,100 個連線):

C (pthread):
  最大延遲:37.57 ms
  P99:29.83 ms
  平均延遲:1.31 ms

與其他客戶端的比較

  • C++ 客戶端:使用具有隱式執行緒池的 std::async
  • Python 客戶端:使用 asyncio 協程(非真實執行緒)
  • Rust 客戶端:使用具有工作竊取排程器的 tokio 非同步執行時

所有這些實作都避免建立過多的系統執行緒。

解決方案:執行緒池實作

實作了具有任務佇列的固定大小執行緒池:

  • 無論連線數量多少,最多 20 個工作執行緒
  • 用於分配工作的任務佇列
  • 工作執行緒從佇列中提取任務
  • 消除過度的執行緒建立和上下文切換

關鍵元件

  1. 執行緒池結構

    • 固定數量的工作執行緒
    • 具有互斥鎖保護的共享任務佇列
    • 用於任務通知的條件變數
  2. 任務佇列

    • 待處理訂單的 FIFO 佇列
    • 動態分配的任務
    • 執行緒安全的入隊/出隊操作
  3. 工作執行緒

    • 閒置時在條件變數上等待
    • 從佇列中處理任務
    • 為多個請求重用執行緒

性能改進結果

之前(原始 pthread 實作)

C 使用 10 個連線:  最大:1.17 ms,P99:1.12 ms
C 使用 50 個連線:  最大:33.83 ms,P99:33.76 ms
C 使用 100 個連線:最大:24.11 ms,P99:23.65 ms

之後(執行緒池實作)

C 使用 10 個連線:  最大:0.58 ms,P99:0.49 ms
C 使用 50 個連線:  最大:0.72 ms,P99:0.64 ms
C 使用 100 個連線:最大:0.69 ms,P99:0.58 ms

最終性能比較(5000 個訂單,100 個連線)

客戶端吞吐量 (req/s)平均延遲 (ms)P99 (ms)最大 (ms)
Python (aiohttp)11,6989.4316.9117.39
C (執行緒池)47,8340.390.701.05
C++ (std::async)25,6410.270.747.05
Rust (tokio)75,6231.292.242.56

關鍵改進

  1. P99 延遲:從 23.65ms 降低到 0.70ms(改善 97%)
  2. 最大延遲:從 37.57ms 降低到 1.05ms(改善 97%)
  3. 吞吐量:增加到 47,834 req/s(現在比 C++ 更快)
  4. 一致性:在所有並發層級上都有穩定的性能

經驗教訓

  1. 執行緒池 > 原始執行緒:對於 I/O 密集型工作負載,固定執行緒池的性能優於為每個連線建立執行緒

  2. 並發 != 並行:更多執行緒並不意味著更好的性能;過多的執行緒會造成排程開銷

  3. libcurl 性能:函式庫本身很快;執行緒模型才是瓶頸

  4. 資源管理:限制活動執行緒可減少上下文切換並提高 CPU 快取效率

  5. 公平比較:比較 HTTP 客戶端函式庫時,並發模型與函式庫本身同樣重要

建議

  • 為高並發 I/O 操作使用執行緒池
  • 對於 I/O 密集型任務,將執行緒數限制在 CPU 核心數的 2-4 倍
  • 考慮使用 async/await 模式以獲得更好的可擴展性
  • 使用實際的並發層級進行效能分析和測試
  • 在性能測試期間監控系統指標(上下文切換、排程器延遲)

結論

執行緒池優化將 C 客戶端從擁有最差的 P99 延遲轉變為實現具有競爭力的性能。這證明瞭適當的並發管理對於高性能網路應用程式至關重要,無論使用哪種底層 HTTP 函式庫。