本文簡單介紹如何在windows程式設計中實現多執行緒類,供大家學習參考,也希望大家指正。

有時候我們想在一個類中實現多執行緒,主執行緒在某些時刻獲得資料,可以“通知”子執行緒去處理,然後把結果返回。下面的例項是主執行緒每隔2s產生10個隨機數,將這10隨機數傳給多執行緒類,讓它接收到資料後馬上打印出來。

首先看類的定義:

#pragma once
#include <iostream>
#include <atlbase.h>    // 使用到了atl類
#include <atlsync.h>
#include <vector>
using namespace std;

class CMultiThreadTest
{
public:
    bool Init();    // 初始化類成員
    bool UnInit();  // 釋放資源
    void NotifyDowork(const std::vector<int> &data);

    static DWORD CALLBACK TestThread(LPVOID);   // 執行緒函式,必須是靜態函式
    DWORD TestProc();                           // 執行緒工作實現

private:
    std::vector<int> m_data;    // 同步資料
    ATL::CEvent m_NotifyEvent;  // 通知事件
    HANDLE m_hThread;           // 執行緒控制代碼
};

類中使用到了ALT類,需要包含atlbase.h和altsync.h標頭檔案。函式TestThread必須是靜態函式,因為CreateThread只接受全域性或者靜態函式。
首先先看Init()和UnInit()函式的實現:
bool CMultiThreadTest::Init()
{
    // 建立事件
    BOOL bRet = m_NotifyEvent.Create(NULL, TRUE, FALSE, NULL);
    if (!bRet) {
        return false;
    }

    // 掛起的方式建立執行緒
    m_hThread = CreateThread(NULL, 0, &CMultiThreadTest::TestThread, this, CREATE_SUSPENDED, NULL);
    if (NULL == m_hThread) {
        return false;
    }

    // 喚醒執行緒
    ResumeThread(m_hThread);
    return true;
}


bool CMultiThreadTest::UnInit()
{
    // 通知執行緒處理data的資料
    if (m_NotifyEvent != NULL) {
        m_NotifyEvent.Set();
    }

    if (m_hThread != NULL) 
    {
        // 預留100ms讓執行緒處理完資料,100ms是個估值
        WaitForSingleObject(m_hThread, 100);
        CloseHandle(m_hThread);
        m_hThread = NULL;
    }

    return true;
}

ATL::CEvent的成員函式Create接收4個引數,第四個引數指定Event的名字(它是可以有名字的),以便在其他程序可以找到該事件,這裡我們不需要使用,把它設定為NULL,其他引數很容易理解,不贅述。
Init()函式值得注意的是我們建立的執行緒是以掛起的方式建立,所以必須呼叫ResumeThread喚醒執行緒,否則執行緒一值處於沉睡狀態,不執行執行緒函式。
UnInit()函式比較簡單,主要通知執行緒執行收尾工作,並釋放類的資源。

下面我們來看看剩下的函式的實現。
DWORD CALLBACK CMultiThreadTest::TestThread(LPVOID lpParam)
{
    if (lpParam == NULL) {
        return 0;
    }

    CMultiThreadTest *lpThis = reinterpret_cast<CMultiThreadTest *>(lpParam);
    return lpThis->TestProc();
}


DWORD CMultiThreadTest::TestProc()
{
    while (true)
    {
        // 每5s監聽一次,秒數直接影響程式的效能
        DWORD dwRet = WaitForSingleObject(m_NotifyEvent, 5000);

        // 進入迴圈5s沒有事件發生,不做任何處理
        if (dwRet == WAIT_TIMEOUT) {
            continue;
        }

	// 列印陣列
        for (unsigned int i = 0; i < m_data.size(); i++)
        {
            cout <<m_data[i] <<" ";
        }

        cout <<endl;

        // 重置事件
        m_NotifyEvent.Reset();
    }
    return 0;
}


void CMultiThreadTest::NotifyDowork(const std::vector<int> &data)
{
    m_data = data;
    m_NotifyEvent.Set();    // 通知執行緒該做事情了!
}

首先我們看TestThread函式,它是執行緒的“入口“,執行緒被喚醒後執行該函式。值得注意的是,我們在建立執行緒的時候把物件指標this作為引數傳遞給建立執行緒函式,系統在呼叫TestThread的時候會把this傳遞回來,這裡使用弱型別轉換reinterpret_cast將LPVOID轉化為CMultiThreadTest類的指標,reinterpret_cast是一個危險的型別轉換,一般只適用於指標和整數之間的轉換。有興趣的同學可以參考C++ Primer第4版18.2.1章節。


執行緒函式將引數lpParam轉化為物件指標後,執行物件的成員函式TestProc(),TestProc()實現主要的邏輯。這裡可能會有人疑問,為什麼不直接在TestThread()函式實現主要邏輯呢?這樣做有兩個好處,一是能夠將執行緒函式邏輯和業務邏輯分離,其二就是TestThread是個靜態函式,類靜態函式只能處理類的靜態成員變數,而很多時候我們希望執行緒處理類的非靜態成員變數。
最後NotifyDowork函式很簡單,該函式給外部呼叫,它把外部傳進來的data賦值給類的非靜態成員變數m_data,並通知執行緒處理m_data資料,TestProc中WaitForSingleObject函式接收到事件後往下執行,把m_data打印出來。

下面我們看看main函式的實現:

int _tmain(int argc, _TCHAR* argv[])
{
    CMultiThreadTest multiThreadTest;
    
    // 初始化失敗
    if (!multiThreadTest.Init()) {
        return 0;
    }

    srand(unsigned int(time(NULL)));
    std::vector<int> data;

    while (true)
    {
        data.clear();

        // 產生10個隨機數
        for (int i = 0; i < 10; i++)
            data.push_back(rand() % 1000);

        // 通知多執行緒類執行工作
        multiThreadTest.NotifyDowork(data);

        Sleep(2000);
    }

    multiThreadTest.UnInit();
    return 0;
}

這段程式碼就不用解釋了,記得包含標頭檔案windows.h、time.h和vector。

總結:
多執行緒類的使用場景是,當一個執行緒或得到資料後,希望其他執行緒能夠處理這部分數。多執行緒類實現還是比較簡單的,首先建立執行緒和執行緒事件,實現給外部呼叫的介面,外部通過介面設定事件,通知執行緒執行。


下一篇文章可能會將更復雜的多執行緒使用場景,涉及到多執行緒同步等技術。