設計動機
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;
}
關鍵技術點解析
- DuplicateHandle:線程結束後原句柄自動關閉,必須複製一份持有,否則 `WaitFor` 失效
- MsgWaitForMultipleObjects:配合 `QS_ALLEVENTS` 可同時等待線程對象和消息隊列,避免 UI 凍結
- PeekMessageLoop:每次等待前處理掛起消息,保持界面流暢
- 非模態對話框模擬模態:`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++ 有更完善的並發庫,但理解其核心邏輯 (句柄複製、消息泵等待、模態模擬) 對維護舊代碼、設計新框架仍有參考價值。