首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

C++ 多線程管理類設計:模板 + 私有繼承 + 安全停止機制

     編輯:關於VC++

設計動機

  • AfxBeginThread 需全局/靜態函數,傳 `this` 指針調用成員函數繁瑣
  • CWinThread 雖是對象但創建/銷毀邏輯分散
  • 線程停止難點:WaitForMultipleObjects 導致 UI 凍結,句柄複製複雜,需等待提示界面

類設計:CYGMulThread<T>

核心結構

template <class T>
class CYGMulThread {
protected:
    typedef void (T::* _pThreadFunc)(void);  // 成員函數指針類型
    static _pThreadFunc ThreadFunc;
    static BOOL s_IsBreaking;                // 全局停止標誌
    static CWaitDlg s_Waitdlg;               // 等待對話框
    static UINT ThreadProc(LPVOID lpParam);  // 靜態線程入口

public:
    static BOOL IsBreakThread();             // 線程內部調用檢查停止信號

protected:
    CYGMulThread();
    virtual ~CYGMulThread();
    CWinThread** m_pThread;                  // 線程指針數組
    int m_nThreadCount;
    void PeekMessageLoop();                  // 消息泵防 UI 凍結

public:
    int GetThreadCount() const;
    void YGBeginFuncThread(_pThreadFunc pFunc, int nThreadCount);  // 創建線程
    bool EndAllThread(const char* szMsg);    // 安全停止所有線程
};

關鍵設計決策

決策理由
模板參數 T 為派生類CRTP (Curiously Recurring Template Pattern) - 基類可調用派生類成員,類似 ATL/WTL 模式
私有繼承封裝性更好,隱藏線程管理細節,對外不暴露基類接口
靜態成員函數指針存儲派生類成員函數地址,線程入口統一調用
靜態停止標誌所有線程共享同一退出信號

使用示例

class CMulThreadDlg : public CDialog, private CYGMulThread<CMulThreadDlg> {
public:
    void ThreadFunc();  // 線程執行函數
    void OnBtnDebug() { YGBeginFuncThread(ThreadFunc, 10); }  // 創建 10 個線程
    void OnBtnStop()  { EndAllThread("正在停止線程..."); }
};

void CMulThreadDlg::ThreadFunc() {
    DWORD dwID = GetCurrentThreadId();
    while (1) {
        TRACE(_T("線程:%x 路過\n"), dwID);
        Sleep(200);
        if (IsBreakThread()) {  // 檢查停止信號
            TRACE(_T("線程:%x 要結束了!\n"), dwID);
            return;
        }
        Sleep(10000);
    }
}

核心難點:安全停止實現

EndAllThread 完整邏輯

bool CYGMulThread<T>::EndAllThread(const char* szMsg) {
    if (!m_pThread) return false;

    HANDLE hProcess = GetCurrentProcess();
    HANDLE* pHandle = new HANDLE[m_nThreadCount];
    T* pWnd = static_cast<T*>(this);

    // 1. 禁用主窗口,顯示模態等待對話框
    pWnd->EnableWindow(FALSE);
    if (szMsg) {
        s_Waitdlg.m_strshow = szMsg;
        s_Waitdlg.SetCancelDisable();
        s_Waitdlg.Create(IDD_YGDIALOG_WAITING, pWnd);
        s_Waitdlg.CenterWindow();
        s_Waitdlg.ShowWindow(SW_SHOW);
    }

    // 2. 複製線程句柄 (DuplicateHandle) - 原句柄線程結束後失效
    for (int i = 0; i < m_nThreadCount; i++) {
        BOOL bRet = DuplicateHandle(hProcess, m_pThread[i]->m_hThread,
                                    hProcess, &pHandle[i],
                                    DUPLICATE_SAME_ACCESS, TRUE, DUPLICATE_SAME_ACCESS);
        if (!bRet) { Sleep(100); i--; continue; }  // 重試
    }

    // 3. 發送停止信號
    s_IsBreaking = TRUE;

    // 4. 逐個等待線程結束 (MsgWaitForMultipleObjects + 消息泵)
    int i = 0;
    while (i != m_nThreadCount) {
        for (i = 0; i < m_nThreadCount; i++) {
            PeekMessageLoop();  // 處理 Windows 消息,保持 UI 響應
            DWORD nRet = MsgWaitForMultipleObjects(1, pHandle + i, FALSE, INFINITE, QS_ALLEVENTS);
            if (nRet == WAIT_OBJECT_0 + 1) { Sleep(20); break; }  // 僅有消息,繼續等待
            if (nRet == WAIT_FAILED) break;
        }
        if (nRet == WAIT_FAILED) break;
    }

    // 5. 清理資源
    delete[] m_pThread;
    delete[] pHandle;
    m_pThread = NULL;
    m_nThreadCount = 0;
    pWnd->EnableWindow(TRUE);
    pWnd->ShowWindow(SW_RESTORE);
    if (IsWindow(s_Waitdlg.m_hWnd)) s_Waitdlg.DestroyWindow();

    return true;
}

關鍵技術點解析

  1. DuplicateHandle:線程結束後原句柄自動關閉,必須複製一份持有,否則 `WaitFor` 失效
  2. MsgWaitForMultipleObjects:配合 `QS_ALLEVENTS` 可同時等待線程對象和消息隊列,避免 UI 凍結
  3. PeekMessageLoop:每次等待前處理掛起消息,保持界面流暢
  4. 非模態對話框模擬模態:`EnableWindow(FALSE)` 禁用父窗口 + 顯示對話框,達到模態效果但不阻塞消息泵

優缺點與改進方向

優點缺點/局限
封裝性好,使用極簡單靜態成員導致所有實例共享停止標誌,不支持多組獨立線程池
解決 UI 凍結、句柄失效、等待提示三大痛點線程函數簽名固定為 `void()`,無參數傳遞
CRTP 模式零虛函數開銷無線程池復用機制,頻繁創建銷毀有開銷
代碼輕量,無外部依賴異常安全性未處理 (線程函數拋出異常會導致進程崩潰)

現代 C++ 改進建議

  • 使用 `std::thread` + `std::atomic` + `std::condition_variable` 替代 Win32 API
  • 實現通用線程池 `ThreadPool` 支持任務隊列、優先級、未來值 (`std::future`)
  • 採用 RAII 管理線程生命週期,析構函數自動 `join()`
  • 使用 `std::function` 支持任意可調用對象 (Lambda、bind、函數對象)

總結

CYGMulThread 以輕量級封裝解決了 MFC 多線程開發中最棘手的「創建簡單、停止困難」問題。其 CRTP + 私有繼承 + 靜態回調的設計模式在 MFC 時代極具代表性,展示了如何在框架限制下實現優雅抽象。雖然現代 C++ 有更完善的並發庫,但理解其核心邏輯 (句柄複製、消息泵等待、模態模擬) 對維護舊代碼、設計新框架仍有參考價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved