Win32執行緒——等待另一個執行緒結束

大囚長發表於2018-12-28

轉載:
https://blog.csdn.net/yss28/article/details/53646627


《Win32多執行緒程式設計》–Jim Beveridge & Robert Wiener

“等待某個什麼東西”是執行緒常常需要做的事。等待是執行緒的“必要之惡”
如果你沒有等待執行緒結束就莽撞地結束程式,執行緒會被系統強制結束掉——在它完成它的工作之前。
由於讓執行緒停工是作業系統的責任,很合理地我們會認為作業系統也有責任讓其他執行緒知道某個執行緒停工了。

Sleep()

這個函式要求作業系統中止執行緒動作,直到渡過某個指定時間之後才恢復。

#include <stdio.h>
#include <Windows.h>

DWORD WINAPI Thread(void *arg) {
    // doing something
    return 0;
}

int main(void) {
    HANDLE hThread = CreateThread(NULL, 0, Thread, NULL, 0, NULL);
    Sleep(?); // 不可能事先知道要等待Thread多久
    CloseHandle(hThread);
    return 0;
}

GetExitCodeThread()輪詢檢查

使用 GetExitCodeThread() 可以決定一個執行緒是否還在執行。

#include <stdio.h>
#include <Windows.h>

DWORD WINAPI Thread(void *arg) {
    // doing something
    return 0;
}

int main(void) {
    DWORD exitCode = 0;
    HANDLE hThread = CreateThread(NULL, 0, Thread, NULL, 0, NULL);
    while (1) {
        GetExitCodeThread(hThread, &exitCode); // 嚴重浪費 CPU 時間
        if (STILL_ACTIVE != exitCode)
            break;
    }
    CloseHandle(hThread);
    return 0;
}

WaitForSingleObject()

可看成一個新版的 Sleep() ,它能夠在某個執行緒結束時(而不是某段時間結束時)被呼叫

可使用的核心物件有兩種狀態:激發未激發。WaitForSingleObject() 會在目標物變成激發狀態時返回。

物件 說明
Thread(執行緒) 當執行緒結束時,執行緒物件即被激發。當執行緒還在進行時,則物件處於未激發狀態。
Process(程式) 當程式結束時,程式物件即被激發。當程式還在進行時,則物件處於未激發狀態。
Event Event 物件的狀態直接受控於應用程式所使用的三個Win32函式:SetEvent()、PulseEvent()、ResetEvent()。CreateEvent()和OpenEvent()都可以傳回一個event object handle。Event物件的狀態也可以被作業系統設定。
Mutex 如果mutex沒有被任何執行緒擁有,它就是處於激發狀態。一旦一個等待mutex的函式返回了,mutex也就自動重置為未激發狀態。
Semaphore Semaphore有點像mutex,但它有個計數器,可以約束其擁有者(執行緒)的個數。當計數器內容大於 0時,semaphore處於激發狀態,當計數器內容等於0時,semaphore處於未激發狀態。
#include <stdio.h>
#include <Windows.h>

DWORD WINAPI Thread(void *arg) {
    // doing something
    return 0;
}

int main(void) {
    HANDLE hThread = CreateThread(NULL, 0, Thread, NULL, 0, NULL);
    WaitForSingleObject(hThread, INFINITE); // 等待,直到執行緒被激發
    CloseHandle(hThread);
    return 0;
}

WaitForMultipleObjects()

允許你在同一時間等待一個以上的物件。你必須將一個由 handles 組成的陣列交給此函式,並指定要等待其中一個物件或是全部的物件

(範例:保持執行緒池中始終有3個執行緒在執行)

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define POOL_SIZE 3 // 執行緒池大小
#define TASK_NUM 6

DWORD WINAPI ThreadFunc(LPVOID n) {
    srand(GetTickCount());

    Sleep(rand()%5000+500);
    printf("%d over\n", n);
    return ((DWORD)n);
}

int main(void){
    HANDLE hThrds[POOL_SIZE];
    int pIdx = 0, tIdx;
    DWORD rc;

    for (tIdx = 1; tIdx <= TASK_NUM; tIdx++) {
        if (tIdx > POOL_SIZE) {
            rc = WaitForMultipleObjects(POOL_SIZE, hThrds, FALSE, INFINITE); // 等待hThrds陣列中任意一個變為激發狀態,返回其索引
            pIdx = rc - WAIT_OBJECT_0;
            assert(pIdx >= 0 && pIdx < POOL_SIZE);
            printf("%d terminated\n", pIdx);
            CloseHandle(hThrds[pIdx]); 
        }

        hThrds[pIdx++] = CreateThread(NULL, 0, ThreadFunc, (LPVOID)pIdx, 0, NULL);
        printf("Thread #%d launched (pIdx %d)\n", tIdx, pIdx);
    }

    WaitForMultipleObjects(POOL_SIZE, hThrds, TRUE, INFINITE); // 等待hThrds陣列中所有執行緒變為激發狀態
    for (pIdx = 0; pIdx < POOL_SIZE; pIdx++)
        CloseHandle(hThrds[pIdx]);

    return EXIT_SUCCESS;
}

結果:
6

GUI 程式中等待

Windows 程式中的“主訊息迴圈”看起來像這個樣子:

while (GetMessage(&msg, NULL, 0, 0,))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

GetMessage() 有點像是特殊版本的 WaitForSingleObject(),它等待訊息
不是核心物件。一旦你呼叫 GetMessage() ,除非有一個訊息真正進入你的訊息
佇列( message queue )之中,否則它不會返回。

如果你在主執行緒中正使用 WaitForSingleO bject()或 WaitForMultipleObjects()等待某個物件被激發,你根本沒有辦法回到主訊息迴圈中去。

為了解決這個問題,主訊息迴圈必須修改,使它得以同時等待訊息或是核心物件被激發。你必須使用一個 MsgWaitForMultipleObjects() 函式。這個函式非常類似WaitForMultipleObjects(),但它會在“物件被激發”或“訊息到達佇列”時被喚醒而返回。MsgWaitForMultipleObjects() 多接受一個引數,允許指定哪些訊息是觀察物件。

相關文章