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

C++ 線程控制實戰(三):_CAsThread 基類核心實現全解析

     編輯:關於C語言

基類設計目標

`_CAsThread` 是整個線程控制體系的基石,負責封裝平台相關的線程操作(Windows `_beginthreadex`/`WaitForSingleObject`/`CloseHandle`),提供統一的啟動/停止/命名/異常處理介面,供上一篇宏生成的繼承類調用。

核心數據成員

class _CAsThread
{
protected:
    bool        m_bExit;        // 線程退出標誌
    bool        m_bStart;       // 線程運行標誌
    HANDLE      m_hThread;      // 線程句柄
    char        m_strName[100]; // 線程名稱(調試用)
    void (*m_pExceptFun)();     // 結構化異常轉換函數指針

public:
    _CAsThread();               // 構造函數初始化
    virtual ~_CAsThread();      // 析構確保線程停止
    virtual void WorkThread() = 0; // 純虛函數,由繼承類實現具體任務

    // 核心控制介面
    bool Start();
    void PrepareStop();
    bool Stop();
    void SetName(const char* vName);
    void SetTranslator(void (*vFun)()); // 設置 SEH 異常轉換器
};

關鍵方法逐行解析

1. 構造/析構:RAII 管理線程生命週期

_CAsThread::_CAsThread()
{
    m_bExit     = false;
    m_bStart    = false;
    m_hThread   = 0;
    memcpy(m_strName, "AsThread", strlen("AsThread")+1);
    m_pExceptFun = NULL;
}

_CAsThread::~_CAsThread()
{
    Stop(); // 析構時自動停止線程,防止資源洩漏
}

2. 線程命名:Visual Studio 調試器專用技巧

// MSVC 專用:通過拋出特定異常設置線程名(僅調試器可見)
#define MS_VC_EXCEPTION 0x406d1388
typedef struct tagTHREADNAME_INFO
{
    DWORD dwType;     // 必須為 0x1000
    LPCSTR szName;    // 線程名字串
    DWORD dwThreadID; // 線程 ID(-1 表示當前線程)
    DWORD dwFlags;    // 保留,必為 0
} THREADNAME_INFO;

void SetThreadName(DWORD dwThreadID, LPCTSTR szThreadName)
{
    THREADNAME_INFO info = { 0x1000, szThreadName, dwThreadID, 0 };
    __try
    {
        RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info);
    }
    __except(EXCEPTION_CONTINUE_EXECUTION) { /* 忽略異常 */ }
}

實用價值: 在 Visual Studio「線程」調試窗口中,線程將顯示為「AsThread」或自定義名稱,而非無意義的十六進制 ID,極大提升多線程調試效率。

3. Start():線程啟動核心邏輯

bool _CAsThread::Start()
{
    if (true == m_bStart) return true; // 已運行,冪等

    if (0 == m_hThread)
    {
        unsigned threadID;
        // _beginthreadex 是 Windows 推薦的線程創建 API(比 CreateThread 正確初始化 CRT)
        m_hThread = (HANDLE)_beginthreadex(NULL, 0, &WorkItem, (void*)this, 0, &threadID);
        
        int tHandle = (int)m_hThread;
        if (0 == tHandle || -1 == tHandle) return false; // 創建失敗

        SetThreadName(threadID, m_strName); // 設置調試器可見名稱
    }

    // 輸出調試啟動信息
    char tDebugBuf[250] = {0};
    #ifndef __BORLANDC__
        sprintf_s(tDebugBuf, "------線程%s開始運行------\n", m_strName);
    #else
        sprintf(tDebugBuf, "------線程%s開始運行------\n", m_strName);
    #endif
    OutputDebugString(tDebugBuf); // 輸出到 VS「輸出」窗口 / DebugView

    m_bExit  = false;
    m_bStart = true;
    return true;
}

4. Stop():優雅停止與資源清理

bool _CAsThread::Stop()
{
    // 1. 設置退出標誌(通知工作循環退出)
    m_bStart = false;
    m_bExit  = true;

    // 2. 等待線程實際結束
    int tHandle = (int)m_hThread;
    if (0 != tHandle && -1 != tHandle)
    {
        WaitForSingleObject(m_hThread, INFINITE); // 無限等待
        CloseHandle(m_hThread);                   // 釋放句柄

        // 輸出調試關閉信息
        char tDebugBuf[250] = {0};
        #ifndef __BORLANDC__
            sprintf_s(tDebugBuf, "------線程%s句柄關閉------\n", m_strName);
        #else
            sprintf(tDebugBuf, "------線程%s句柄關閉------\n", m_strName);
        #endif
        OutputDebugString(tDebugBuf);
    }

    m_hThread = 0;
    return true;
}

5. WorkItem():靜態線程入口點,驅動虛函數多態

unsigned __stdcall _CAsThread::WorkItem(void* vContext)
{
    _CAsThread* pTimer = (_CAsThread*)vContext;

    // 設置結構化異常轉換器(將 SEH 異常轉為 C++ 異常)
    pTimer->SetTranslator();

    // 工作循環
    while (1)
    {
        if (true == pTimer->m_bExit) break; // 檢查退出標誌
        pTimer->WorkThread();               // 調用繼承類實現的具體任務
    }

    // 輸出退出調試信息
    char tDebugBuf[250] = {0};
    #ifndef __BORLANDC__
        sprintf_s(tDebugBuf, "------線程%s開始退出------\n", pTimer->m_strName);
    #else
        sprintf(tDebugBuf, "------線程%s開始退出------\n", pTimer->m_strName);
    #endif
    OutputDebugString(tDebugBuf);

    return 0;
}

6. 異常處理增強:SEH 轉 C++ 異常

void _CAsThread::SetTranslator()
{
    if (NULL == m_pExceptFun) return;

    // 禁用系統錯誤彈窗,改為程序捕獲
    SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
    // 設置轉換函數:當硬體異常(訪問違例、除零等)發生時,調用 m_pExceptFun
    _set_se_translator(m_pExceptFun);
}

應用場景: 防止線程內硬體異常(如空指針訪問)導致整個進程崩潰無日誌,轉為 C++ 異常後可在 `WorkThread()` 中 `try-catch` 捕獲並記錄日誌、優雅退出。

完整類圖與協作關係

classDiagram
    class _CAsThread {
        -m_bExit: bool
        -m_bStart: bool
        -m_hThread: HANDLE
        -m_strName: char[100]
        -m_pExceptFun: SETranslator
        +Start() bool
        +Stop() bool
        +PrepareStop()
        +SetName()
        +SetTranslator()
        #WorkThread() void*  // 純虛函數
        +WorkItem() unsigned // 靜態入口
    }

    class CTestThreadTest {
        -m_Owner: CTestThread*
        +WorkThread() void
    }

    _CAsThread <|-- CTestThreadTest : 繼承
    CTestThreadTest --> CTestThread : m_Owner 指向
    CTestThread : +Test() void

使用檢查清單(避坑指南)

  1. 必須設置 `m_Owner`:宏生成的類實例化後,首要任務是 `m_Thread.m_Owner = this;`,否則 `WorkThread()` 內解引用空指針崩潰
  2. 析構順序:宿主對象析構前,線程必須已 `Stop()`(基類析構會自動調用,但確保 `m_Owner` 生命週期 ≥ 線程對象)
  3. WorkThread() 耗時操作:長時間計算/阻塞 IO 應定期檢查 `m_bExit`,響應停止請求
  4. 異常安全:`WorkThread()` 內建議包裹 `try-catch(...)`,防止異常逃逸導致進程終止
  5. 跨平台限制:本實現強依賴 Windows API(`_beginthreadex`、`WaitForSingleObject`、`RaiseException`),Linux/macOS 需用 `pthread` 重寫

系列總結與現代化建議

三篇文章構建了完整的「宏驅動成員函數綁定 + 基類封裝平台差異」線程框架,展示了 C++98/03 時代的經典工程智慧。但在 **C++11 及以上** 專案中,請優先選用標準庫:

  • std::thread + Lambda / std::bind:類型安全、無宏魔法、異常傳播標準
  • std::jthread (C++20):自動 `join`、支援 `stop_token` 協作式取消
  • std::async`/`std::future`:任務並行、返回值獲取更直觀
  • std::mutex`/`std::condition_variable`/`std::atomic`:同步原語完備

理解經典實現原理,是為了更好地駕馭現代工具。願這系列文章能幫你在多線程編程的道路上走得更穩、更遠!

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