等待线程WaitForSingleObject()、WaitForMultipleObjects()
在多线程下面,有时候我们会希望等待某一线程完成了再继续做其他事情,要实现这个目的,可以使用Windows API函数WaitForSingleObject,或者WaitForMultipleObjects。这两个函数都会等待Object被标为有信号(signaled)时才返回的。
那么,什么是信号呢?
简单来说,Windows下创建的Object都会被赋予一个状态量。如果Object被激活了,或者正在使用,那么该Object就是无信号,也就是不可用;另一方面,如果Object可用了,那么它就恢复有信号了。
这两个函数的优点是它们在等待的过程中会进入一个非常高效沉睡状态,只占用极少的CPU时间片。(这两个函数都是在内核状态下等待内核对象,不切换到用户模式下,因而效率很高)
WaitForSingleObject
1. 格式
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds);
有两个参数,分别是THandle和Timeout(毫秒单位)。当等待仍在挂起状态时,句柄被关闭,那么函数行为是未定义的。该句柄必须具有 SYNCHRONIZE 访问权限,如果想要等待一条线程,那么你需要指定线程的Handle,以及相应的Timeout时间。当然,如果你想无限等待下去,Timeout参数可以指定系统常量INFINITE。WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回
2. 参数
参数1:等待的对象:Event(事件),Mutex(互斥量),Semaphore(信号量),Process(进程),Thread(线程) 等
参数2:dwMilliseconds为等待的时间,它有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止
3. 返回类型
有三种返回类型:
WAIT_OBJECT_0 0x00000000, 表示等待的对象有信号(对线程来说,表示执行结束);
WAIT_TIMEOUT 0x00000102 等待超时,表示等待指定时间内,对象一直没有信号(线程没执行完);
WAIT_ABANDONED 0x00000080 当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值
示例:
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;
DWORD WINAPI FunProc(LPVOID lpParameter)
{
int i = 1;
for (; i < 1000; i++)
{
printf(" %d", i);
if (!(i % 10))
printf("\n");
}
return 0;
}
int main()
{
HANDLE hThread;
hThread = (HANDLE)CreateThread(NULL, 0, FunProc, NULL, 0, NULL);
DWORD dwRet = WaitForSingleObject(hThread, 1);
if (dwRet == WAIT_OBJECT_0)
{
printf("创建线程执行结束\n");
}
if (dwRet == WAIT_TIMEOUT)
{
printf("等待超时\n");
}
if (dwRet == WAIT_ABANDONED)
{
printf("Abandoned\n");
}
CloseHandle(hThread);
return 0;
}
WaitForMultipleObjeccts
相对来说,WaitForMultipleObjects要复杂点点
1. 格式:
DWORD WaitForMultipleObjects(
DWORD nCount,
CONST HANDLE * lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds);
WaitForMultipleObjects是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象
2. 参数:
参数1:用于指定句柄数组的数量,对象句柄的最大数量为MAXIMUM_WAIT_OBJECTS。此参数不能为零
参数2:一组对象句柄中用于指定句柄数组的内存地址,该数组可以包含不同类型对象的句柄。它可能不包含同一句柄的多个副本。如果其中一个句柄在等待仍处于暂挂状态时关闭,则该函数的行为未定义。句柄必须具有SYNCHRONIZE访问权限。
参数3:如果此参数为TRUE则在lpHandles数组中的所有对象的状态发出信号时,该函数返回。如果为FALSE,则当任何一个对象的状态设置为信号时,该函数返回。在后一种情况下,返回值表示其状态导致函数返回的对象。
参数4:用于指定等待的Timeout时间,单位毫秒,设置为0则它立即返回,是INFINITE则仅在发出指定对象信号时才返回该函数。
3. 返回值:
WAIT_OBJECT_0到(WAIT_OBJECT_0 + nCount - 1如果bWaitAll为TRUE),则返回值表明所有指定对象的状态信号。如果bWaitAll为FALSE,则返回值减去WAIT_OBJECT_0表示lpHandles数组的序号。如果同时有多个内核对象被触发,这个函数返回的只是其中序号最小的那个。
WAIT_ABANDONED_0至(WAIT_ABANDONED_0 + nCount - 1)如果bWaitAll为TRUE,则返回值表明所有指定对象的状态是触发的,并且至少对象之一,是一个废弃的互斥对象。如果bWaitAll为FALSE,则返回值减去WAIT_ABANDONED_0 表示一个废弃的互斥对象在lpHandles数组中的下标
WAIT_TIMEOUT表示时间隔已过,由bWaitAll参数指定的条件得不到满足
WAIT_FAILED则表示函数执行失败,会设置GetLastError
WAIT_IO_COMPLETION:(仅适用于WaitForMultipleObjectsEx)由于一个I/O完成操作已作好准备执行,所以造成了函数的返回
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;
HANDLE m_hEvent[2];
DWORD WINAPI Fun(LPVOID pM)
{
while (1)
{
int i;
}
return 0;
}
DWORD WINAPI Fun1(LPVOID pM)
{
//while (1)
//{
int i;
//}
return 0;
}
int main()
{
m_hEvent[0] = (HANDLE)CreateThread(NULL, 0, Fun, NULL, 0, NULL);
m_hEvent[1] = (HANDLE)CreateThread(NULL, 0, Fun1, NULL, 0, NULL);
int index = WaitForMultipleObjects(2, m_hEvent, FALSE, 500);
if (index == WAIT_OBJECT_0)
{
cout << "Fun返回" << endl;
}
else if (index == WAIT_OBJECT_0 + 1)
{
cout << "Fun1返回" << endl;
}
else if (index == WAIT_TIMEOUT)
{
cout << "超时" << endl;
}
return 0;
}
注意:
举个例子:我们需要在一个线程中处理从完成端口、数据库、和可等待定时器来的数据。一个典型的实现方法就是:用WaitForMultipleObjects等待所有的这些事件。如果完成端口,数据库发过来的数据量非常大,可等待定时器时间也只有几十毫秒。那么这些事件同时触发的几率可以说非常大,我们不希望丢弃任何一个被触发的事件。那么如何能高效地实现这一处理呢?多个内核对象被触发时,WaitForMultipleObjects选择其中序号最小的返回。而WaitForMultipleObjects它只会改变使它返回的那个内核对象的状态。这儿又会产生一个问题,如果序号最小的那个对象频繁被触发,那么序号比它大的内核对象将得不到被处理的机会。为了解决这一问题,可以采用双WaitForMultipleObjects检测机制来实现。见下面的例子:
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
DWORD dwRet = 0;
int nIndex = 0; //nIndex相当于数组pHandles的下标
while (1)
{
//参数3设置为FALSE则有满足的对象就返回
dwRet = WaitForMultipleObjects(nCount, pHandles, FALSE, INFINITE);
switch (dwRet)
{
case WAIT_TIMEOUT:
break;
case WAIT_FAILED:
return 1;
default:
{
nIndex = dwRet - WAIT_OBJECT_0;
ProcessHanlde(nIndex++); //做某个线程结束后的相关操作处理的函数,然后将下标值nIndex进行++
//继续按照数组的方向向下检测
while (nIndex < nCount) //nCount事件对象总数
{
//继续检测数组中除了第一个已经返回的线程,剩下线程的返回情况,参数1:nCount-nIndex即为剩下的数量
//参数2:为剩下线程中的首地址
dwRet = WaitForMultipleObjects(nCount - nIndex, &pHandles[nIndex], FALSE, 0);
switch (dwRet)
{
case WAIT_TIMEOUT:
nIndex = nCount; //退出检测,因为没有被触发的对象了,相当于已经从头到尾进行过一遍检测以及处理
break;
case WAIT_FAILED:
return 1;
default:
{
//因为nIndex是相对于整个数组的下标,所以要进行这样的赋值,然后继续沿数组方向进行检测
nIndex = nIndex + dwRet - WAIT_OBJECT_0;
ProcessHanlde(nIndex++);
}
break;
}
}
}
break;
}
}
return 0;
}