多線程編程中,手動追蹤 HANDLE、等待結束、CloseHandle 易疏漏導致資源洩漏。本文設計 「線程自管」 架構:CThreadManager 負責建立業務線程並登記至監控器,CThreadTask 內含專用監控線程定期掃描已結束線程並自動釋放句柄。使用者僅需提供線程函式與參數,其餘全自動。

CThreadManager(threadFunc, param) → 內部 CreateThread 建立業務線程。CThreadManager 建構時自動啟動單例 CThreadTask 監控器 (若未啟動)。HANDLE 登記至 CThreadTask::m_ObList。ThreadTaskProc) 每 500ms 喚醒,遍歷列表呼叫 GetExitCodeThread,發現 STILL_ACTIVE != exitCode 即 CloseHandle 並從列表移除。RunThread() 開始執行 (對應 ResumeThread)。typedef DWORD (WINAPI *ThreadProc)(LPVOID);
CThreadManagerclass CThreadManager : public CObject
{
DECLARE_DYNAMIC(CThreadManager)
public:
CThreadManager(ThreadProc proc, LPVOID param);
~CThreadManager();
BOOL RunThread(); // 恢復線程執行
HANDLE GetThreadHandle() const { return m_Handle; }
private:
HANDLE m_Handle; // 業務線程句柄
};
CThreadTask (單例)class CThreadTask : public CObject
{
DECLARE_DYNAMIC(CThreadTask)
public:
CThreadTask(); ~CThreadTask();
BOOL IsValid(); // 檢查監控線程是否已啟動
void AddHandle(CONST HANDLE h); // 登記業務線程句柄
void CloseThreadHandles(); // 掃描並清理已結束線程
static CThreadTask& GetThreadTask(); // 單例存取點
CObList m_ObList; // 儲存 CHandle* 列表
HANDLE m_Handle; // 監控線程自身句柄
BOOL m_bKeepGoing; // 監控迴圈旗標
};
CHandleclass CHandle : public CObject
{
public:
HANDLE m_ThreadHandle;
};
CThreadTask::CThreadTask()
{
m_bKeepGoing = TRUE;
DWORD tid = 0;
m_Handle = (HANDLE)::CreateThread(NULL, 0, ThreadTaskProc, 0, 0, &tid);
}
void CThreadTask::AddHandle(CONST HANDLE h)
{
CHandle* p = new CHandle;
p->m_ThreadHandle = h;
m_ObList.AddTail(p);
}
void CThreadTask::CloseThreadHandles()
{
if (m_ObList.GetCount() == 0) return;
POSITION pos1, pos2;
CHandle* pHandle = NULL;
DWORD dwExitCode = 0;
for (pos1 = m_ObList.GetHeadPosition(); (pos2 = pos1) != NULL; )
{
pHandle = DYNAMIC_DOWNCAST(CHandle, m_ObList.GetNext(pos1));
VERIFY(::GetExitCodeThread(pHandle->m_ThreadHandle, &dwExitCode));
if (dwExitCode != STILL_ACTIVE) // 線程已結束
{
m_ObList.RemoveAt(pos2);
VERIFY(::CloseHandle(pHandle->m_ThreadHandle));
delete pHandle;
pHandle = NULL;
}
}
}
DWORD WINAPI ThreadTaskProc(LPVOID)
{
HANDLE hCur = GetCurrentThread();
SetThreadPriority(hCur, THREAD_PRIORITY_LOWEST); // 低優先級,不搶佔業務
while (CThreadTask::GetThreadTask().m_bKeepGoing)
{
Sleep(500);
CThreadTask::GetThreadTask().CloseThreadHandles();
}
SetThreadPriority(hCur, THREAD_PRIORITY_NORMAL);
return 0;
}
CThreadManager::CThreadManager(ThreadProc proc, LPVOID param)
{
_ASSERTE(CThreadTask::GetThreadTask().IsValid()); // 確保監控器就緒
DWORD tid = 0;
m_Handle = (HANDLE)::CreateThread(NULL, 0, proc, param, CREATE_SUSPENDED, &tid);
// CREATE_SUSPENDED:建立後暫停,等 RunThread() 再恢復
if (CThreadTask::GetThreadTask().m_bKeepGoing)
{
ASSERT(m_Handle);
CThreadTask::GetThreadTask().AddHandle(m_Handle); // 登記給監控器
}
}
BOOL CThreadManager::RunThread()
{
return ::ResumeThread(m_Handle) != 0xFFFFFFFF;
}
DWORD WINAPI MyWorker(LPVOID p)
{
// 耗時任務...
return 0;
}
void SomeFunction()
{
CThreadManager mgr(&MyWorker, (LPVOID)this);
mgr.RunThread(); // 線程啟動,結束後自動清理句柄,無需手動 CloseHandle
}
| 優點 | 缺點/注意 |
|---|---|
| 使用極簡單:兩行代碼啟動自管線程 | 監控線程輪詢 500ms,線程結束到句柄釋放有延遲 |
| 自動防洩漏:無需記住 CloseHandle | 無法取得線程退出碼 (若需可擴充 AddHandle 傳入回調) |
| 封裝為 DLL 可跨專案重用 | MFC 依賴 (CObject, CObList),純 SDK 專案需調整 |
| 支援暫停啟動 (CREATE_SUSPENDED) | 單例監控器進程結束時需正確清理 (析構函數待完善) |
WaitForMultipleObjects + RegisterWaitForSingleObject (執行緒池) 替代輪詢,即時回收零延遲。std::function/Lambda 支援 (C++11),擺脫靜態函式指標限制。GetExitCode()、完成回調、例外傳播機制。「線程自管」思想將資源管理責任轉移給基礎設施層,業務代碼專注於邏輯。本文提供的 CThreadManager/CThreadTask 雖屬早期 MFC 風格,但核心模式 (生產者登記、專用回收器掃描) 仍具參考價值,可作為現代 C++ std::thread/std::jthread + 自定義 RAII 封裝的靈感來源。
本文配套原始碼可供下載。