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

多執行緒程式設計範例解析:從時鐘顯示到檔案複製的並行思維

     編輯:VC 教學

前言

理論再豐富,不如一個能跑的範例。本文以兩個經典案例——**對話框時鐘**與**非阻塞檔案複製**——帶你走完「建立執行緒 → 執行任務 → 安全終止 → UI 回應」完整閉環,並對比單執行緒的痛點。

案例一:對話框時鐘 (Worker Thread + 簡單終止)

專案建立

  1. MFC AppWizard → Dialog based → 專案名 MultiThreadClock
  2. 對話框放兩個 Button (IDC_START/IDC_STOP, Caption「啟動」/「停止」) + 一個 Edit (IDC_TIME)

核心代碼


// MultiThreadClockDlg.h
class CMultiThreadClockDlg : public CDialog {
    HANDLE m_hThread;
    DWORD  m_dwThreadId;
    HANDLE m_hStopEvent;          // 優雅終止訊號
    // ...
};

// MultiThreadClockDlg.cpp
UINT __cdecl ClockThread(LPVOID pParam) {
    CMultiThreadClockDlg* pDlg = (CMultiThreadClockDlg*)pParam;
    while (WaitForSingleObject(pDlg->m_hStopEvent, 0) != WAIT_OBJECT_0) {
        CTime t = CTime::GetCurrentTime();
        CString str = t.Format(_T("%H:%M:%S"));
        // 跨執行緒更新 UI 必須用 PostMessage 或 SendMessage
        pDlg->PostMessage(WM_USER_UPDATE_TIME, 0, (LPARAM)(new CString(str)));
        Sleep(1000);
    }
    return 0;
}

void CMultiThreadClockDlg::OnStart() {
    m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 手動重置、初始非觸發
    m_hThread = CreateThread(NULL, 0, ClockThread, this, 0, &m_dwThreadId);
}

void CMultiThreadClockDlg::OnStop() {
    SetEvent(m_hStopEvent);          // 通知執行緒退出
    WaitForSingleObject(m_hThread, INFINITE); // 等待實際結束
    CloseHandle(m_hThread); m_hThread = NULL;
    CloseHandle(m_hStopEvent); m_hStopEvent = NULL;
}

// 訊息處理
#define WM_USER_UPDATE_TIME (WM_USER + 100)
BEGIN_MESSAGE_MAP(CMultiThreadClockDlg, CDialog)
    ON_MESSAGE(WM_USER_UPDATE_TIME, OnUpdateTime)
END_MESSAGE_MAP()

LRESULT CMultiThreadClockDlg::OnUpdateTime(WPARAM, LPARAM lParam) {
    CString* pStr = (CString*)lParam;
    SetDlgItemText(IDC_TIME, *pStr);
    delete pStr; // 記得釋放
    return 0;
}

關鍵點解析

  • 優雅終止:用 Event 而非 TerminateThread,讓執行緒自行清理資源
  • 跨執行緒更新 UI:嚴禁直接呼叫 SetDlgItemText,必須 PostMessage 丟給 UI 執行緒處理
  • 參數傳遞:CreateThread 的 lpParameter 傳 this,執行緒函式轉型取用

案例二:非阻塞檔案複製 (UI 執行緒 + Worker Thread + 進度回報)

痛點回顧

單執行緒複製大檔案時,UI 凍結,「取消」按鈕點不動,使用者以為死機狂點。多執行緒讓複製在背景跑,UI 保持流暢。

架構設計


struct CopyParam {
    CString src, dst;
    HWND    hProgress;      // 進度條控制代碼
    HANDLE  hStopEvent;     // 取消訊號
    HANDLE  hDoneEvent;     // 完成訊號
};

UINT __cdecl CopyThread(LPVOID pParam) {
    CopyParam* p = (CopyParam*)pParam;
    CFile srcFile(p->src, CFile::modeRead | CFile::shareDenyWrite);
    CFile dstFile(p->dst, CFile::modeCreate | CFile::modeWrite);
    ULONGLONG total = srcFile.GetLength(), done = 0;
    BYTE buf[64*1024]; // 64KB 緩衝

    while (done < total) {
        if (WaitForSingleObject(p->hStopEvent, 0) == WAIT_OBJECT_0) break;
        UINT nRead = srcFile.Read(buf, sizeof(buf));
        if (nRead == 0) break;
        dstFile.Write(buf, nRead);
        done += nRead;
        int percent = (int)(done * 100 / total);
        ::SendMessage(p->hProgress, PBM_SETPOS, percent, 0); // 直接送訊息更新進度條
    }
    srcFile.Close(); dstFile.Close();
    SetEvent(p->hDoneEvent);
    return 0;
}

// UI 啟動
void OnCopy() {
    param.hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    param.hDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    param.hProgress = GetDlgItem(IDC_PROGRESS)->m_hWnd;
    AfxBeginThread(CopyThread, ¶m);
}

// UI 取消
void OnCancel() { SetEvent(param.hStopEvent); }

// 等待完成 (可在另一執行緒或定時器輪詢)
WaitForSingleObject(param.hDoneEvent, INFINITE);

進階優化:IOCP 非同步複製

對超大檔 (GB 級) 或高併發,改用 FILE_FLAG_OVERLAPPED + ReadFileEx/WriteFileEx + 完成例程,完全無阻塞,CPU 佔用極低。

多執行緒設計三大黃金法則 (範例驗證版)

  1. 共享資料最小化:案例一只共享 m_hStopEvent、案例二只共享 CopyParam 結構體
  2. 同步原語正確選用:單一布林旗標用 Event、複雜資料結構用 CriticalSection/Mutex、生產消費用 Semaphore
  3. UI 執行緒神聖不可侵犯:所有控制項操作 **只能** 在 UI 執行緒 (透過訊息/委派) 執行

效能權衡:何時「不」用多執行緒

  • 任務極短 (毫秒級) → 執行緒建銷開銷 > 任務本身
  • 高度 CPU 密集且核心數有限 → 切換開銷抵消並行收益
  • 大量鎖競爭 → 序列化反而更快
  • 簡單腳本/工具 → 複雜度不值得

結語

從一個每秒更新的時鐘,到一個可隨時取消的大檔複製,多執行緒的價值具象化為:**使用者感知不到的後台工作,與永不凍結的前台體驗**。掌握 CreateThread/AfxBeginThread、Event/Mutex、PostMessage 三板斧,配合「優雅啟動、優雅終止、跨執行緒 UI 安全」三原則,你就能駕馭 90% 的桌面並行場景。下一步,請嘗試將案例二改寫為 std::thread + std::atomic + lambda 現代 C++ 風格,感受 RAII 帶來的安心。

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