原文連接-線程池的自我修養 mysql
最近重構行情服務端的框架,其中有一部分就是重寫mysql線程池,線程池是一個很獨立的東西,今天就拿出來給你們分享, 怎樣設計一個線程池, 以及我是怎麼作的.常見的線程池使用場景分爲兩種程序員
這個很好理解, 當程序須要大量計算, 單核CPU跑到100%, 這個時候能夠將計算任務分解, 分多個線程計算, 若是咱們有4核, 那這個時候咱們能夠跑到400%, 理想狀況下, 能夠節省3倍的時間. 固然這個不是絕對的, 具體狀況要具體分析. 總而言之, 是爲了讓程序充分打滿CPU.web
若是這個是web程序, 異步絕對是提升併發的神器. 在咱們的C++服務器中, 也會有大量的阻塞任務, 多是讀取mysql, 多是讀取mongodb, 或者任意須要同步等待完成的事情, 那麼在等待的時候, 咱們的工做線程是徹底無法作別的工做的, 這個時候咱們就把等待的過程, 變成一個任務, 讓線程池去作, 主線程繼續處理別的工做, 等線程池完成以後, 再接管任務, 繼續往下面執行.sql
這是兩種徹底不一樣的工做內容, 看上去都是線程池, 須要注意的細節, 是徹底不同的, 好比開啓的線程數量, 大量計算的時候, 咱們開的線程, 儘可能是小於CPU數量的, mysql訪問的時候, 線程數必定是不能高於mysql的併發數的. 這種細節不少, 不一樣的情景狀況不同, 不能一律而就.mongodb
今天我要給你們分享的線程池, 拋開任務的細節, 主要講咱們應該怎樣去設計一個線程池.編程
無論任務多麼複雜, 最終都在這個模型上. 重點能夠分爲下面幾個:緩存
每一個點的設計, 不一樣的人有不一樣的方法, 向你們分享個人方法, 主要針對的是mysql線程池的設計, 僅供你們參考.bash
線程間通訊有不少種方法, 多是信號, 多是管道, 多是套接字, 我比較喜歡更高級的封裝zmq. 無論怎樣的通訊方式, 咱們須要保證下面兩點:服務器
不論是主工做線程與調度線程之間, 仍是調度線程與線程池線程之間, 必定是異步完成, 絕對不容許同步, 任何地方有同步邏輯, 將成爲整個線程池的瓶頸.多線程
一個請求, 只能返回一次, 絕對不能一問多答, 更不能只問不答. 線程池要向主工做線程保證, 過來的請求, 必定會返回, 而且有且只有一次返回. 同時我建議, 若是線程池內部發生執行異常, 不要作二次嘗試, 直接將異常標記返回.
通訊模塊的設計, 要保證簡單高效, 給外面暴露的接口簡單到只有接收任務和發送結果兩個接口, 過多冗餘的設計, 只是無畏的增長了複雜度.
先上張圖
調度線程須要關注的也是兩點:
這部分我也喜歡交給zmq去作, 有任何消息的時候直接回調, 這裏我將外部主線程消息與線程池消息都放在一個消息隊列, 既符合先進先出的模式, 也符合單線程同步執行的邏輯.
當過來的任務超過線程池真實併發數量的時候, 咱們會將任務緩存在隊列, 而後當工做線程執行完任務的時候,或者有新的任務過來的時候, 咱們都會去檢查是否有空餘的工做線程, 而後將任務分配給工做線程.
將全部的工做抽象成通用的任務, 得益於C++的類型轉換, 咱們能夠將全部的入參, 和出參都打包成一個void*, 而後將具體執行任務的過程, 使用一個靜態函數, 這樣打包一個通用的工做任務.
/**
* @brief 給db層發送的參數
*/
struct DBParam
{
DBParam():
m_type(fund_begin),
m_seq(0)
{}
//! 須要執行的sql
std::string m_str_sql;
//! db的類型
db_res_type m_type;
//! 請求的seq
uint64_t m_seq;
};
/**
* @brief 從db返回的數據
*/
class ResFund:
public ResBase
{
public:
ResFund(){}
//! 基礎數據集合
std::vector<FundInfo> m_vec_funds;
};
//! 交給各個服務的正真執行sql的回調函數
typedef void (*DBQueryHandler)(MYSQL* con, void* param, void* res)
class DbMessage:
public MessageBase{
public:
/**
* @brief 構造函數
*/
DbMessage();
/**
* @brief 析構函數
*/
virtual ~DbMessage();
//! 須要執行的參數
void* m_params;
//! 執行以後, 產生的結果信息
void* m_msg;
//! 執行mysql的回調
DBQueryHandler m_handle_fun;
};
複製代碼
上面的代碼刪除了一些敏感的信息, 將主體拿出來, 大體表示我是怎麼打包一個任務的. 事實上無論線程池作得多麼的好, 業務變幻無窮, 咱們很難知足的, 而咱們這個任務的封裝最主要的就是把業務封裝到任務裏面, 咱們經過一個DBQueryHandler的回調函數, 主工做線程將本身的業務寫到回調裏面, 交由工做線程完成, 進而實現業務的變幻無窮.
看過大多線程池的實現, 不少人都喜歡用鎖, 好比消息隊列, 任務隊列, 用各類鎖來競爭, 進而實現任務的分發, 不敢說這個性能怎麼樣, 可是一旦扯上鎖, 整個代碼複雜度就上去了, 一處用鎖, 處處加鎖. 這個線程池的設計是徹底沒有任何鎖的, 單線程內部徹底是消息驅動, 線程間消息投遞, 簡單高效.
線程池的設計見仁見智, 不一樣的設計可能基於不一樣的需求, 沒有銀彈. 可是必定要把接口設計得簡單, 不要有酷炫吊炸天的功能, 良好的文檔, 對使用者友好, 一眼就能看懂的接口, 纔是咱們要追求的, 一句話, 簡單,簡單,再簡單.
如您對個人文章感興趣,請訂閱如下公衆號, 我將給您講述, 中小企業程序員, 淘金路上的故事.