线程等待 WaitFor...

来源:互联网 发布:四大办公软件 编辑:程序博客网 时间:2024/06/02 10:57

一:BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode);

该函数可以决定一个线程是否还在执行,只要不断的检测返回值就可以判断线程是否结束。但是不断的检测会浪费CPU事件。常说的busy loop就是这种现象。

二:DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

hHandle:等待对象的句柄(代表一个核心对象),如线程句柄。
dwMilliseconds:时间间隔,即使对象未激活,时间到了还是会返回
1:此函数返回成功的三个因素:
1)等待的目标(核心对象)变成激发状态,返回WAIT_OBJECT_0;
2)核心对象变成激发状态前,等待时间到了,返回WAIT_TIMEOUT;
3)如果一个拥有MUTEX(互斥器)的线程结束前没有释放MUTEX,则传回WAIT_ABANDONED。

当线程正在执行时,线程对象处于未激活状态。当线程结束时,线程对象就会被激活,函数WaitForSingleObject就会醒来。需要注意的是:醒来未必立即被调动,而只是从休眠态队列排入就绪队列,等待操作系统调度。
HANDLE g_hEvent;
UINT _stdcall ChildFunc(LPVOID);
int main(int argc, char* argv[])
{
    HANDLE hChildThread;
    UINT uId;

    // 创建一个自动重置的事件
    g_hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);

    hChildThread = (HANDLE)::_beginthreadex(NULL, 0, ChildFunc, NULL, 0, &uId);

    //用户输入字符后,就会激活时间
    getchar();
    ::SetEvent(g_hEvent);

    // 等待子线程完成工作,激活线程对象
    ::WaitForSingleObject(hChildThread, INFINITE);

    ::CloseHandle(hChildThread);
    ::CloseHandle(g_hEvent);
    return 0;
}
UINT _stdcall ChildFunc(LPVOID)
{
    //等待事件被激活,
    ::WaitForSingleObject(g_hEvent, INFINITE);
    printf(" Child thread is working....../n");
    ::Sleep(5*1000);
    return 0;
}

2:那么哪些对象对象是被激发的对象。

对于线程/进程,如果还在运行,该对象就处于未激发;如果运行结束,则转入激发状态。
对于文件对象,如果一个I/O操作完成,该文件对象即处于激发状态;
对于事件,CreateEvent()初态为TRUE,或SetEvent()、PulseEvent(),事件为激发态;
对于互斥器,如果未被任何进程锁定,该对象处于激发状态;
对于信号量,如果信号量的现值大于0,该对象处于激发状态;
对于控制台输入,当Console窗口的输入缓冲区中有数据可用时,此对象被激发。

三:DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles, BOOL fWaitAll,DWORD dwMilliseconds );

nCount 句柄的数量 最大值为MAXIMUM_WAIT_OBJECTS(64)
HANDLE 句柄数组的指针。不需要为相同类型
HANDLE 类型可以为(Event,Mutex,Process,Thread,Semaphore )数组
BOOL bWaitAll 等待的类型,如果为TRUE 则等待所有信号量有效在往下执行,FALSE 当有其中一个信号量有效时就向下执行
DWORD dwMilliseconds 超时时间 超时后向执行。 如果为WSA_INFINITE 永不超时。如果没有信号量就会在这死等。
1:返回值
1)等待时间到了,返回WAIT_TIMEOUT;
2)当bWaitAll是True的时候正常返回值是 WAIT_OBJECT_0;
3)当bWaitAll是false的时候,返回值减去WAIT_OBJECT_0,就是表示数组中哪一个Handle被激活了。
4)如果等待对象中有Mutex,则传回值范围从WAIT_ABANDONED_0到WAIT_ABANDONED_0 + nCount – 1。

DWORD WINAPI ThreadFunc(LPVOID);
#define THREAD_POOL_SIZE 3
#define MAX_THREAD_INDEX THREAD_POOL_SIZE-1
#define NUM_TASKS 6

int main()
{
    HANDLE  hThrds[THREAD_POOL_SIZE];
    int     slot = 0;
    DWORD   threadId;
    int     i;
    DWORD   rc;

    for (i=1; i<=NUM_TASKS; i++)
    {
        if (i > THREAD_POOL_SIZE)
        {
            //只要有一个线程被激活了,就返回
            rc = WaitForMultipleObjects( THREAD_POOL_SIZE,hThrds,FALSE,INFINITE );
            //获取被激活的线程
            slot = rc - WAIT_OBJECT_0;
        }
        //创建线程,赋值给刚刚被激活的线程数组
        hThrds[slot] = CreateThread(NULL,0,ThreadFunc,(LPVOID)slot,0,&threadId )
            slot++;
    }

    //等待所有线程都结束后,推出
    rc = WaitForMultipleObjects(THREAD_POOL_SIZE,hThrds,TRUE,INFINITE );

    for (slot=0; slot<THREAD_POOL_SIZE; slot++)
        CloseHandle(hThrds[slot]);
    return EXIT_SUCCESS;
}

DWORD WINAPI ThreadFunc(LPVOID n)
{
    srand( GetTickCount() );
    Sleep((rand()%10)*800+500);
    printf("Slot %d idle/n", n);
    return ((DWORD)n);
}

四:DWORD MsgWaitForMultipleObjects(DWORD nCount, LPHANDLE pHandles, BOOL fWaitAll,  DWORD dwMilliseconds, DWORD dwWakeMask  );

如果只是使用WaitForSingleObjects()或WaitForMultipleObjects(),你根本就无法回到主消息循环里。这样,应用程序的用户界面停止重绘,菜单工具条无法响应。

dwWakeMask :添加到对象数组中的句柄,pHandles除了用户在数组总制定的外,还可以添加消息的激活事件对象。
1:返回值,多一个WAIT_OBJECT_0+ nCount,表示“指定的消息到达队列”。
2:当主线程要退出时,为了能保证线程的资源能全部地释放,主线程必须等待工作线程退出。线程对象和进程对象一样,也是内核对象,而且线程对象的特点是当线程退出时,线程内核对象会自动变为有信号状态,能够唤醒所有正在等待它的线程。我们通常都习惯于使用WaitForSingleObject等函数来等待某个内核对象变为有信号状态,但是在主线程中不要使用WaitForSingleObject和WaitForMultipleObjects两个函数等待线程退出,其原因就是有导致程序死锁的隐患,特别是线程函数里调用了SendMessage或是直接操作了MFC对象,更易出现此种现象。
比如,在主线程中调WaitForSingleObject等子线程结束,而此时CPU切换到子线程函数体内执行,而子线程函数体这是可能的某个步骤需要住线程完成,这是通过SendMesage发送消息给主线程,由于主线程已经挂起,所以没有机会去消息队列中抓取消息并处理它,结果导致函数不会返回,工作线程也被挂起,从而导致死锁。

int nWaitCount = 2
while (1)
{
    dRet=MsgWaitForMultipleObjects(nWaitCount,hArray,FALSE,INFINITE,QS_ALLINPUT);
    if (dRet == WAIT_OBJECT_0+ nWaitCount)
    {
        //当函数由于消息到来而返回,则需要用户主动去消息队列中将消息抓取出来,然后派发出去,这样该消息就会被处理了。
        while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    else if (dRet >= WAIT_OBJECT_0 && dRet < WAIT_OBJECT_0+ nWaitCount)
    {
        nExitThreadCount++;
        //退出了一个线程
        if (nExitThreadCount == 1)
        {
            //获取哪个线程退出,并从对象数组中去除该线程,同时修改对象数。
            int nIndex=dRet-WAIT_OBJECT_0;
            hArray[nIndex]=hArray[nWaitCount-1];
            hArray[nWaitCount-1]=NULL;
            nWaitCount--;
        }
        else
        {
            //两个线程都退出了
            break;
        }
    }
    else
    {
        DWORD dErrCode=GetLastError();
        break;
    }
}

原创粉丝点击