Windows 95/NT 引入的預emptive 多任務架構,讓開發者能精確控制程式碼片段的執行排程,編寫高效能、回應迅速的應用程式。多任務包含「多進程」與「多執行緒」兩大類:進程是資源擁有單位,執行緒則是 CPU 排程的基本單元。一個進程至少擁有一條主執行緒,透過建立額外執行緒即可實現進程內並行。本文聚焦於 VC++ 5.0 MFC 類庫實現多執行緒調度與同步的完整方法論,並附帶詳細實戰範例。
CWinApp 派生類別,主執行緒即屬此類。對 Win32 API 而言,兩者無本質差異,僅需提供執行緒啟動位址。但 MFC 要求所有使用 MFC 機制的執行緒必須透過 AfxBeginThread 建立,否則執行緒區域儲存(TLS)與 MFC 內部狀態將無法正確初始化。
CWinThread 核心角色CWinThread 物件維護執行緒專屬資料(如 m_hThread、m_nThreadID、m_pMainWnd 等)。所有 MFC 執行緒最終都對應一個 CWinThread 實例,由框架自動管理生命週期(預設 m_bAutoDelete = TRUE)。
UINT MyFunc(LPVOID pParam)。參數可傳遞結構體指標以實現雙向資料交換。返回 0 表示成功,非零為錯誤碼。AfxBeginThread(MyFunc, pParam, nPriority, dwStackSize, dwCreateFlags, lpSecurityAttrs) 返回 CWinThread*。UINT MyThreadProc(LPVOID pParam) {
CMyObject* pObj = (CMyObject*)pParam;
if (!pObj || !pObj->IsKindOf(RUNTIME_CLASS(CMyObject))) return -1;
// 具體工作...
return 0;
}
// 呼叫端
CMyObject* pNew = new CMyObject;
AfxBeginThread(MyThreadProc, pNew);
CWinThread 派生類別,加上 DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE 巨集,再呼叫 AfxBeginThread(RUNTIME_CLASS(CMyUIThread), ...)。new CMyUIThread 建構物件,再呼叫 CreateThread() 啟動。此時需手動設定 m_bAutoDelete = FALSE 並自行 delete 物件。Run() 結束,框架自動清理。AfxEndThread(nExitCode),釋放資源。TerminateThread——它會立即殺死執行緒,導致資源洩漏、關鍵區未釋放、C++ 解構函數不被呼叫,嚴重破壞程序穩定性。若被終止的是進程內最後一條執行緒,進程也將隨之結束。
Windows 採 32 級優先級(0~31):
執行緒實際優先級 = 進程基礎優先級類別 + 執行緒相對優先級。
| 進程類別 | 常數 |
|---|---|
| 閒置 | IDLE_PRIORITY_CLASS |
| 正常 | NORMAL_PRIORITY_CLASS |
| 高 | HIGH_PRIORITY_CLASS |
| 即時 | REALTIME_PRIORITY_CLASS |
| 相對優先級 | 常數 |
|---|---|
| 最低(僅系統閒置執行) | THREAD_PRIORITY_IDLE |
| 低於正常 | THREAD_PRIORITY_BELOW_NORMAL |
| 正常(預設) | THREAD_PRIORITY_NORMAL |
| 高於正常 | THREAD_PRIORITY_ABOVE_NORMAL |
| 最高 | THREAD_PRIORITY_HIGHEST |
| 關鍵(極高) | THREAD_PRIORITY_TIME_CRITICAL |
調整範例:SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);pThread->SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL)。
多執行緒共享資源時,若缺乏同步,將產生競爭條件:讀取到半更新的資料、雙重釋放記憶體、邏輯錯誤等。MFC 封裝了完整的同步物件體系:
CSyncObject)Lock(),解構時自動 Unlock(),支援 RAII 模式,防止忘記釋放。Lock(DWORD dwTimeOut, BOOL bWaitForAll))。將同步物件封裝為共享類別的資料成員,在公開方法中使用 CSingleLock 保護關鍵區。例如:
class CAccountList {
CCriticalSection m_cs;
CList m_list;
public:
void AddAccount(CAccount* p) {
CSingleLock lock(&m_cs);
lock.Lock();
m_list.AddTail(p);
}
CAccount* GetAccount(int idx) {
CSingleLock lock(&m_cs);
lock.Lock();
return m_list.GetAt(m_list.FindIndex(idx));
}
};
優點:封裝性好、維護容易、符合 OOP 原則。代價:單核 CPU 上上下文切換開銷、除錯複雜度上升。
對話框應用程式,包含兩條 UI 執行緒:計數執行緒 持續對共享字串 m_strNumber 執行 +1 操作;顯示執行緒 將該字串即時顯示於列表框。提供進程/執行緒優先級調整、同步機制啟停、執行緒掛起/恢復功能,直觀展示競爭條件與同步效果。
CExampleThread : public CWinThread:基底類別,持有對話框指標 m_pOwner、運行旗標 m_bDone、SetOwner() 方法。CCounterThread、CDisplayThread:分別重寫 Run() 實作業務邏輯,內部使用 CSingleLock 搭配對話框的 CMutex m_mutex 保護共享字串。Run()int CCounterThread::Run() {
CSingleLock sLock(&(m_pOwner->m_mutex));
while (!m_bDone) {
BOOL fSync = m_pOwner->IsDlgButtonChecked(IDC_SYNCHRONIZE);
if (fSync) sLock.Lock();
unsigned int n = _ttoi(m_pOwner->m_strNumber);
n++;
m_pOwner->m_strNumber.Format(_T("%u"), n);
if (fSync) sLock.Unlock();
if (m_pOwner->IsDlgButtonChecked(IDC_SHOWCNTRTHRD))
m_pOwner->AddToListBox(_T("Counter: Add 1"));
}
m_pOwner->PostMessage(WM_CLOSE);
return 0;
}
Run()int CDisplayThread::Run() {
CSingleLock sLock(&(m_pOwner->m_mutex));
CString strBuf;
while (!m_bDone) {
BOOL fSync = m_pOwner->IsDlgButtonChecked(IDC_SYNCHRONIZE);
if (fSync) sLock.Lock();
strBuf = _T("Display: ") + m_pOwner->m_strNumber;
if (fSync) sLock.Unlock();
m_pOwner->AddToListBox(strBuf);
}
m_pOwner->PostMessage(WM_CLOSE);
return 0;
}
BOOL CMutexesDlg::OnInitDialog() {
// ... 初始化三個優先級下拉框(進程、計數、顯示)預設 Normal ...
// 建立執行緒,初始掛起
m_pDisplayThread = (CDisplayThread*)AfxBeginThread(
RUNTIME_CLASS(CDisplayThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
m_pDisplayThread->SetOwner(this);
m_pCounterThread = (CCounterThread*)AfxBeginThread(
RUNTIME_CLASS(CCounterThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
m_pCounterThread->SetOwner(this);
// ...
}
// 進程優先級
void CMutexesDlg::OnSelchangePriorityclass() {
DWORD dwPri[] = {IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS};
SetPriorityClass(GetCurrentProcess(), dwPri[((CComboBox*)GetDlgItem(IDC_PRIORITYCLASS))->GetCurSel()]);
}
// 執行緒優先級(統一處理)
void CMutexesDlg::OnPriorityChange(UINT nID) {
DWORD dwPri[] = {THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST,
THREAD_PRIORITY_TIME_CRITICAL};
int idx = ((CComboBox*)GetDlgItem(nID))->GetCurSel();
if (nID == IDC_CNTRTHRDPRIORITY) m_pCounterThread->SetThreadPriority(dwPri[idx]);
else m_pDisplayThread->SetThreadPriority(dwPri[idx]);
}
// 掛起/恢復
void CMutexesDlg::OnPause() {
BOOL bPause = ((CButton*)GetDlgItem(IDC_PAUSE))->GetCheck();
if (bPause) { m_pCounterThread->SuspendThread(); m_pDisplayThread->SuspendThread(); }
else { m_pCounterThread->ResumeThread(); m_pDisplayThread->ResumeThread(); }
}
void CMutexesDlg::OnClose() {
// 若掛起中,先恢復
if (((CButton*)GetDlgItem(IDC_PAUSE))->GetCheck()) {
((CButton*)GetDlgItem(IDC_PAUSE))->SetCheck(0);
m_pCounterThread->ResumeThread();
m_pDisplayThread->ResumeThread();
}
int nActive = 0;
DWORD dwExit;
if (m_pCounterThread && ::GetExitCodeThread(m_pCounterThread->m_hThread, &dwExit) && dwExit == STILL_ACTIVE) {
m_pCounterThread->m_bDone = TRUE; nActive++;
} else if (m_pCounterThread) { delete m_pCounterThread; m_pCounterThread = NULL; }
if (m_pDisplayThread && ::GetExitCodeThread(m_pDisplayThread->m_hThread, &dwExit) && dwExit == STILL_ACTIVE) {
m_pDisplayThread->m_bDone = TRUE; nActive++;
} else if (m_pDisplayThread) { delete m_pDisplayThread; m_pDisplayThread = NULL; }
if (nActive == 0) CDialog::OnClose();
else PostMessage(WM_CLOSE); // 遞歸等待,避免阻塞 UI
}
AfxBeginThread、CWinThread、同步類別,避免直接呼叫 Win32 API 導致 MFC 狀態不一致。TerminateThread:採用旗標/事件協同終止,保證 RAII 與解構函數正確執行。CSingleLock 建構即鎖定,解構自動釋放,杜絕忘記 Unlock。PostMessage 回報進度。SetThreadDescription 或命名慣例)、啟用「中斷點過濾器」針對特定執行緒中斷。掌握上述核心概念與實戰模式,即可在 VC++ MFC 專案中自信地駕馭多執行緒,編寫出高效、穩定、可維護的並行應用程式。