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

VC++ MFC 多執行緒程式設計全攻略:從基礎架構到同步實戰

     編輯:關於VC++

前言:多執行緒在 Windows 應用中的核心地位

Windows 95/NT 引入的預emptive 多任務架構,讓開發者能精確控制程式碼片段的執行排程,編寫高效能、回應迅速的應用程式。多任務包含「多進程」與「多執行緒」兩大類:進程是資源擁有單位,執行緒則是 CPU 排程的基本單元。一個進程至少擁有一條主執行緒,透過建立額外執行緒即可實現進程內並行。本文聚焦於 VC++ 5.0 MFC 類庫實現多執行緒調度與同步的完整方法論,並附帶詳細實戰範例。

MFC 對多執行緒的支援模型

兩類執行緒的區別

  • 輔助執行緒:無訊息迴圈,專司背景運算、資料處理、檔案 I/O 等長時間任務。
  • 使用者介面執行緒:擁有獨立訊息泵,能處理視窗訊息、使用者輸入與 UI 互動。典型代表是 CWinApp 派生類別,主執行緒即屬此類。

對 Win32 API 而言,兩者無本質差異,僅需提供執行緒啟動位址。但 MFC 要求所有使用 MFC 機制的執行緒必須透過 AfxBeginThread 建立,否則執行緒區域儲存(TLS)與 MFC 內部狀態將無法正確初始化。

CWinThread 核心角色

CWinThread 物件維護執行緒專屬資料(如 m_hThread、m_nThreadID、m_pMainWnd 等)。所有 MFC 執行緒最終都對應一個 CWinThread 實例,由框架自動管理生命週期(預設 m_bAutoDelete = TRUE)。

執行緒的建立與終止

輔助執行緒:兩步走

  1. 實作控制函數:原型 UINT MyFunc(LPVOID pParam)。參數可傳遞結構體指標以實現雙向資料交換。返回 0 表示成功,非零為錯誤碼。
  2. 啟動執行緒: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);

使用者介面執行緒:兩種建立方式

  1. 動態建立法:從 CWinThread 派生類別,加上 DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE 巨集,再呼叫 AfxBeginThread(RUNTIME_CLASS(CMyUIThread), ...)。
  2. 手動建立法:先 new CMyUIThread 建構物件,再呼叫 CreateThread() 啟動。此時需手動設定 m_bAutoDelete = FALSE 並自行 delete 物件。

執行緒終止的正確姿勢

  • 自然結束:控制函數返回或 Run() 結束,框架自動清理。
  • 執行緒內主動結束:呼叫 AfxEndThread(nExitCode),釋放資源。
  • 外部請求終止:建立通訊機制(事件、旗標),讓執行緒自行檢查並退出。嚴禁 使用 TerminateThread——它會立即殺死執行緒,導致資源洩漏、關鍵區未釋放、C++ 解構函數不被呼叫,嚴重破壞程序穩定性。

若被終止的是進程內最後一條執行緒,進程也將隨之結束。

優先級體系與排程策略

Windows 採 32 級優先級(0~31):

  • 0~15:動態優先級。高優先級搶佔 CPU,同級時間片輪轉。
  • 16~31:即時優先級。同級不輪轉,先搶佔者持續佔用 CPU 直到主動放棄。

執行緒實際優先級 = 進程基礎優先級類別 + 執行緒相對優先級。

進程類別常數
閒置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)

  • CSemaphore:計數訊號,限制同時存取資源的執行緒數(如連線池、多檢視文檔)。
  • CMutex:互斥體,跨進程可用,確保同一時間僅一執行緒擁有資源(如共享 DLL 中的全域資料)。
  • CCriticalSection:臨界區,僅限進程內,效率最高,適合高頻短暫存取保護。
  • CEvent:事件訊號,用於執行緒間等待/通知(如生產者-消費者模型)。

同步存取輔助類別

  • CSingleLock:鎖定單一同步物件,建構時自動 Lock(),解構時自動 Unlock(),支援 RAII 模式,防止忘記釋放。
  • CMultiLock:同時等待多個同步物件(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
}

總結與最佳實踐

  1. 優先使用 MFC 封裝:AfxBeginThread、CWinThread、同步類別,避免直接呼叫 Win32 API 導致 MFC 狀態不一致。
  2. 拒絕 TerminateThread:採用旗標/事件協同終止,保證 RAII 與解構函數正確執行。
  3. 同步粒度要適中:臨界區保護最小程式碼段,避免在鎖內呼叫阻塞 API 或跨執行緒送訊息。
  4. 善用 RAII 鎖類別:CSingleLock 建構即鎖定,解構自動釋放,杜絕忘記 Unlock。
  5. UI 執行緒與背景執行緒分離:耗時運算放入輔助執行緒,UI 執行緒專心處理訊息,透過 PostMessage 回報進度。
  6. 除錯技巧:使用「執行緒」視窗檢視呼叫堆疊、命名執行緒(SetThreadDescription 或命名慣例)、啟用「中斷點過濾器」針對特定執行緒中斷。

掌握上述核心概念與實戰模式,即可在 VC++ MFC 專案中自信地駕馭多執行緒,編寫出高效、穩定、可維護的並行應用程式。

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