Windows高级编程之线程与内核对象的同步

用户方式同步的优点是它的同步速度非常快。如果强调线程的运行速度,那么首先应该确定用户方式的线程同步机制是否适合需要。
用户方式线程同步机制的局限性:
1、互锁函数家族只能在单值上运行
2、关键代码段只能对单个进程中的线程实施同步
3、关键代码段容易陷入死锁状态,因为无法设定超时值。
内核对象机制的适应性远远优于用户方式机制。不足之处在于速度较慢,并且需要从用户方式转为内核方式
当进程正在运行的时候,进程内核对象处于未通知状态,当进程终止运行的时候,它就变为已通知状态。进程内核对象中是个布尔值,当对象创建时,该值被初始化为 FA L S E(未通知状态) 。当进程终止运行时,操作系统自动将对应的对象布尔值改为T R U E,表示该对象已经得到通知。
即线程内核对象总是在未通知状态中创建。当线程终止运行时,操作系统会自动将线程对象的状态改为已通知状态。
下面的内核对象可以处于已通知状态或者未通知状态
进程/文件修改通知/线程/事件/作业/可等待定时器/文件/信标/控制台输入/互斥对象
线程可以使自己进入等待状态,直到一个对象变为已通知状态。注意,用于控制每个对象的已通知/未通知状态的规则要根据对象的类型而定。
当线程等待的对象处于未通知状态中时,这些线程不可调度。但是一旦对象变为已通知状态,线程看到该标志变为可调度状态,并且很快恢复运行。
9.1   等待函数
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。这些等待函数中最常用的是WaitForSingleObject:
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
第一个参数hObject标识一个能够支持被通知 /未通知的内核对象(前面列出的任何一种对象都适用) 。第二个参数dwMilliseconds允许该线程指明,为了等待该对象变为已通知状态,它将等待多长时间。INFINITE表示无限等待
WaitForSingleObject(hProcess,INFINITE);调用函数准备等待到hProcess句柄标识的进程终止运行为止:
WaitForSingleObject的返回值能够指明调用线程为什么再次变为可调度状态。如果线程等待的对象变为已通知状态,那么返回值是WAIT_OBJECT_0。如果设置的超时已经到期,则返回值是WAIT_TIMEOUT。如果将一个错误的值(如一个无效句柄)传递给WaitForSingleObject,那么返回值将是WAIT_FAILED。
DWORD WaitForMultipleObjects(DWORD dwCount,CONST HANDLE *phObjects,BOOL fWaitAll,DWORD dwMilliseconds);
WaitForMultipleObjects允许调用线程同时查看若干个内核对象的已通知状态。
dwCount参数用于指明想要让函数查看的内核对象的数量。这个值必须在 1与MAXIMUM_WAIT_OBJECTS(在Windows头文件中定义为64)之间。phObjects参数是指向内核对象句柄的数组的指针。
fWaitAll为TRUE表示让线程进入等待状态,直到所有指定的内核对象都变为已通知状态。
        为FALSE表示让线程进入等待状态,直到指定内核对象中的任何一个变为已通知状态。
WaitForMultipleObjects的返回值为WAIT_FAILED和WAIT_TIMEOUT。如果fWaitAll为TRUE,同时所有对象均变为已通知状态,那么返回值是WAIT_OBJECT_0。如果fWaitAll为FALSE,那么一旦任何一个对象变为已通知状态,该函数便返回,返回值是WAIT_OBJECT_0与(WAIT_OBJECT_0+dwCount-1)之间的一个值。换句话说,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么应该从返回值中减去WAIT_OBJECT_0。产生的数字是作为第二个参数传递给WaitForMultipleObjects的句柄数组中的索引。该索引说明哪个对象变为已通知状态。
如果为fWaitAll参数传递FALSE,WaitForMultipleObjects就从索引0开始向上对句柄数组进行扫描,同时已通知的第一个对象终止等待状态。
9.2   成功等待的副作用
成功地调用WaitForSingleObject和WaitForMultipleObjects,实际上会改变对象的状态。成功地调用是指函数发现对象已经得到通知并且返回一个相对于WAIT_OBJECT_0的值,即对象的状态已经改变。
例子:两个线程以完全相同的方式来调用WaitForMultipleObjects:
HANDLE h[2];
h[0] = hAutoResetEvent1; //Initially nonsignaled
h[1] = hAutoResetEvent2; //Initially nonsignaled
WaitForMultipleObjects(2,h,TRUE,INFINITE);
当WaitForMultipleObjects函数被调用时,两个事件都处于未通知状态,这就迫使两个线程都进入等待状态。然后hAutoResetEvent1对象变为已通知状态。两个线程都发现,该事件已经变为已通知状态,但是它们都无法被唤醒,因为hAutoResetEvent2仍然处于未通知状态。由于两个线程都没有等待成功,因此没有对hAutoResetEvent1对象产生任何副作用。接着,hAutoResetEvent2变为已通知状态。这时,两个线程中的一个发现,两个对象都变为已通知状态。等待取得了成功,两个事件对象均被置为未通知状态,该线程变为可调度的线程。但是另一个线程的情况如何呢?它将继续等待,直到它发现两个事件对象都处于已通知状态。尽管它原先发现hAutoResetEvent1处于已通知状态,但是现在它将该对象视为未通知状态。
问题在于多个线程等待单个内核对象,那么当该对象变成已通知状态时,系统究竟决定唤醒哪个线程呢?Microsoft采用的公平算法:意味着如果多个线程正在等待,那么每当对象变为已通知状态时,每个线程都应该得到它自己的被唤醒的机会。这意味着线程的优先级不起任何作用,即高优先级线程不一定得到该对象。这还意味着等待时间最长的线程不一定得到该对象。同时得到对象的线程有可能反复循环,并且再次得到该对象。
9.3   事件内核对象
在所有的内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样) ,一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。
事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。
HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,BOOL fInitialState,PCTSTR pszName);
fManualReset参数是个布尔值,它能够告诉系统是创建一个人工重置的事件(TRUE)还是创建一个自动重置的事件(FALSE) 。fInitialState参数用于指明该事件是要初始化为已通知状态(TRUE)还是未通知状态(FALSE) 。当系统创建事件对象后,CreateEvent就将与进程相
关的句柄返回给事件对象。其他进程中的线程可以获得对该对象的访问权,方法是使用在pszName参数中传递的相同值。使用继承性,使用DuplicateHandle函数等来调用CreateEvent,或者调用OpenEvent,在pszName参数中设定一个与调用CreateEvent时设定的名字相匹配的名字:
HANDLE OpenEvent(DWORD fdwAccess,BOOL fInherit,PCTSTR pszName);
当不再需要事件内核对象时,应该调用CloseHandle函数。
一旦事件已经创建,就可以直接控制它的状态。当调用SetEvent时,可以将事件改为已通知状态:
BOOL SetEvent(HANLDE hEvent);
当调用ResetEvent函数时,可以将该事件改为未通知状态:
BOOL ResetEvent(HANDLE hEvent);
Microsoft为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。这就是自动重置的事件如何获得它们的名字的方法。通常没有必要为自动重置的事件调用ResetEvent函数,因为系统会自动对事件进行重置。但是,Microsoft没有为人工重置的事件定义成功等待的副作用。
//Create a global handle to a manual-reset,nonsignaled event
HANLDE g_hEvent;
int WINAPI WinMain(...){
  //Create the manual-reset,nonsignaled event
  g_hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
 
//Spawn 3 new threads
HANDLE hThread[3];
DWORD dwThreadID;
hThread[0] = _beginthreadex(NULL,0,WordCount,NULL,0,&dwThreadID);
hThread[1] = _beginthreadex(NULL,0,SpellCheck,NULL,0,&dwThreadID);
hThread[2] = _beginthreadex(NULL,0,GrammarCheck,NULL,0,&dwThreadID);

OpenFileAndReadContentsIntoMemory(..)
//Allow all 3 thread to access the memory.
SetEvent(g_hEvent);
}
DWORD WINAPI WordCount(PVOID pvParam){
    //Wait until the file's data is in memory
    WaitForSingleObject(g_hEvent,INFINITE);
    //Access the memory block
    ...
    return(0);
}
如果你使用自动重置的事件而不是人工重置的事件,那么应用程序的行为特性就有很大的差别。当主线程调用SetEvent之后,系统只允许一个辅助线程变成可调度状态。
注意,当使用自动重置事件时,如果每个辅助线程均以读 /写方式访问内存块,那么就不会产生任何问题,这些线程将不再被要求将数据视为只读数据。
BOOL PulseEvent(HANLDE hEvent);
PulseEvent函数使得事件变为已通知状态,然后立即又变为未通知状态,这就像在调用SetEvent后又立即调用ResetEvent函数一样。如果在人工重置的事件上调用PulseEvent函数,那
么在发出该事件时,等待该事件的任何一个线程或所有线程将变为可调度线程。如果在自动重置事件上调用PulseEvent函数,那么只有一个等待该事件的线程变为可调度线程。如果在发出事件时没有任何线程在等待该事件,那么将不起任何作用。
9.4   等待定时器内核对象
等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。它们通常用来在某个时间执行某个操作。
HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,PCTSTR pszName);
fManualReset参数用于指明人工重置的定时器或自动重置的定时器。当发出人工重置的定时器信号通知时,等待该定时器的所有线程均变为可调度线程。当发出自
动重置的定时器信号通知时,只有一个等待的线程变为可调度线程。
CreateWaitableTimer创建等待定时器。
进程可以获得它自己的与进程相关的现有等待定时器的句柄,方法是调用OpenWaitableTimer函数:
HANLDE OpenWaitableTimer(DWORD dwDesireAccess,BOOL hInheritHandle,PCTSTR pszName);
等待定时器对象总是在未通知状态中创建。必须调用SetWaitableTimer函数来告诉定时器你想在何时让它成为已通知状态:
BOOL SetWaitableTimer(HANDLE hTimer,const LARGE_INTEGER *pDueTime,LONG lPeriod,PTIMERAPCROUTINE pfnCompletionRoutine,PVOID pvArgToCompletionRoutine,BOOL fResume);
hTimer参数用于指明你要设置的定时器,pDueTime和lPeriod两个参数是一道使用的,pDueTimer参数用于指明定时器何时应该第一次报时,lPeriod参数则用于指明此后定时器应该间隔多长时间报时一次。
SetWaitableTimer希望传递给它的时间始终都采用世界协调时(U T C)的时间。
//下面代码用于将定时器的第一次报时的时间设置在2 0 0 2年1月1日的下午1点钟,然后每隔6小时报时一次:
//Declare our local variable
HANDLE hTimer;
SYSTEMTIME st;
FILETIME ftLocal,ftUTC;
LARGE_INTEGER liUTC;
//Create an auto-reset timer;
hTimer = CreateWaitable(NULL,FALSE,NULL);
//First signaling is at 202-1-1 1:00PM(Local Time)
st.wYear = 2002;st.wMonth = 1;st.wDayOfWeek = 0;st.wDay=1;st.wHour=13;st.wMinute=0;st.wSecond=0;st.wMilliseconds=0;

SystemTimeToFileTime(&st,&ftLocal);
//Convert local time to UTC time
LocalFileTimeToFileTime(&ftLocal,&ftUTC);
//Convert FILETIME to LARGE_INTEGER because of different alignment
liUTC.LowPart = ftUTC.dwLowDateTime;
liUTC.HighPart = ftUTC.dwHighDateTime;
//Set the Timer
SetWaitableTimer(hTimer,&liUTC,6*60*60*1000,NULL,NULL,FALSE);
虽然FILETIME和LARGE_INTEGER结构采用相同的二进制格式,但是这两个结构的调整要求不同。所有FILETIME结构的地址必须从一个3 2位的边界开始,而所有LARGE_INTEGER结构的地址则必须从6 4位的边界开始。如果不设置定时器应该第一次报时的绝对时间,也可以让定时器在一个相对于调用SetWaitableTimer的时间进行报时。只需要在pDueTime参数中传递一个负值。传递的值必须是以100ns为间隔。
通常情况下,你可能想要一个一次报时的定时器,它只是发出一次报时信号,此后再也不发出报时信号。若要做到这一点,只需要为lPeriod参数传递0即可。然后可以调用CloseHandle
函数,关闭定时器,或者再次调用SetWaitableTimer函数,重新设置时间,为它规定一个需要遵循的新条件。SetWaitableTimer的最后一个参数是fResume,它可以用于支持暂停和恢复的计算机。通常可以为该参数传递FALSE,
BOOL CancelWaitableTimer(HANDLE hTimer);
用于取出定时器的句柄并将它撤消,除非接着调用SetWaitableTimer函数以便重新设置定时器,否则定时器决不会进行报时。如果想要改变定时器的报时条件,不必在调用SetWaitableTimer函数之前调用CancelWaitableTimer函数。每次调用SetWaitableTimer函数,都会在设置新的报时条件之前撤消定时器原来的报时条件。
9.4.1   让等待定时器给APC项排队
Microsoft还允许定时器给在定时器得到通知信号时调用SetWaitableTimer函数的线程的异步过程调用(APC)进行排队。
一般来说,当调用SetWaitableTimer函数时,你将同时为pfnCompletionRoutine和pvArgCompletionRoutine参数传递N U L L。当SetWaitableTimer函数看到这些参数的N U L L时,它
就知道,当规定的时间到来时,就向定时器发出通知信号。但是,如果到了规定的时间,你愿意让定时器给一个A P C排队,那么你必须传递定时器A P C例程的地址,例程必须自己实现,类似于下面的形式:
VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine,DWORD dwTimerLowValue,DWORD dwTimerHighValue){
   //do whatever you want here
}
该函数可以在定时器报时的时候由调用SetWaitableTimer函数的同一个线程来调用,但是只有在调用线程处于待命状态下才能调用。换句话说,该线程必须正在SleepEx/WaitForSingleObjectEx/WaitForMultipleObjectsEx,MsgWaitForMultipleObjectsEx/SingleObject-AndWait等函数的调用中等待。如果该线程不在这些函数中的某个函数中等待,系统将不给定时器APC例程排队。这可以防止线程的APC队列中塞满定时器APC通知,这会浪费系统中的大量内存。当定时器报时的时候,如果你的线程处于待命的等待状态中,系统就使你的线程调用回调例程。回调例程的第一个参数的值与你传递给SetWaitableTimer函数的pvArgToCompletionRoutine参数的值是相同的,剩余的两个参数dwTimerLowValue和dwTimerHighValue用于指明定时器何时报时。
只有当所有的A P C项都已经处理之后,待命的函数才会返回。因此,必须确保定时器再次变为已通知状态之前,TimerAPCRoutine函数完成它的运行,这样,A P C项的排队速度就不会比它被处理的速度快。
最后要说明的是,线程不应该等待定时器的句柄,也不应该以待命的方式等待定时器。
9.4.2   定时器的松散特性
定时器常常用于通信协议中。客户机通常要同时与许多服务器进行通信。如果你为每个请求创建一个定时器内核对象,那么系统的运行性能就会受到影响。可以设想,对于大多数应用程序来说,可以创建单个定时器对象,并根据需要修改定时器报时的时间。
定时器报时时间的管理方法和定时器时间的重新设定是非常麻烦的,只有很少的应用程序采用这种方法。使用CreateTimerQueueTimer函数
虽然定时器能够给APC项进行排队是很好的,但是目前编写的大多数应用程序并不使用APC,它们使用I / O完成端口机制。例如:线程池中(由一个 I / O完成端口负责管理)有一个线程,它按照特定的定时器间隔醒来。但是,等待定时器没有提供这个方法。为了做到这一点,我创建了一个线程,它的唯一工作是设置而后等待一个等待定时器。当定时器变为已通知状态时,线程就调用PostQueuedCompletionStatus函数,将一个事件强加给线程池中的一个线程。
SetWaitableTimer与SetTimer的区别是:用户定时器需要在应用程序中设置许多附加的用户界面结构,这使定时器变得资源更加密集。另外,等待定时器属于内核对象,这意味着它们可以供多个线程共享,并且是安全的。用户定时器能够生成WM_TIMER消息,这些消息将返回给调用SetTimer(用于回调定时器)的线程和创建窗口(用于基于窗口的定时器)的线程。因此,当用户定时器报时的时候,只有一个线程得到通知。另一方面,多个线程可以在等待定时器上进行等待,如果定时器是个人工重置的定时器,则可以调度若干个线程。
如果要执行与用户界面相关的事件,以便对定时器作出响应,那么使用用户定时器来组织代码结构可能更加容易些,因为使用等待定时器时,线程必须既要等待各种消息,又要等待内
核对象。最后,运用等待定时器,当到了规定时间的时候,更有可能得到通知。
9.5   信标内核对象
信标内核对象用于对资源进行计数。它们与所有内核对象一样,包含一个使用数量,但是它们也包含另外两个带符号的3 2位值,一个是最大资源数量,一个是当前资源数量。最大资源
数量用于标识信标能够控制的资源的最大数量,而当前资源数量则用于标识当前可以使用的资源的数量。
比如说,我正在开发一个服务器进程,在这个进程中,我已经分配了一个能够用来存放客户机请求的缓冲区。我对缓冲区的大小进行了硬编码,这样它每次最多能够存放5个客户机请求。如果5个请求尚未处理完毕时,一个新客户机试图与服务器进行联系,那么这个新客户机的请求就会被拒绝,并出现一个错误,指明服务器现在很忙,客户机应该过些时候重新进行联系。当我的服务器进程初始化时,它创建一个线程池,里面包含 5个线程,每个线程都准备在客户机请求到来时对它进行处理。开始时,没有客户机提出任何请求,因此我的服务器不允许线程池中的任何线程成为可调度线程。使用信标,就能够很好地处理对资源的监控和对线程的调度,最大资源数量设置为 5,当前资源数量最初设置为0,因为没有客户机提出任何请求。当客户机的请求被接受时,当前资源数量就递增,当客户机的请求被提交给服务器的线程池时,当前资源数量就递减。
信标的使用规则如下:
• 如果当前资源的数量大于0,则发出信标信号。
• 如果当前资源数量是0,则不发出信标信号。
• 系统决不允许当前资源的数量为负值。
• 当前资源数量决不能大于最大资源数量。
HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,LONG lInitialCount,LONG lMaxmumCount,PCTSTR pszName);
HANDLE OpenSemaphore(DWORD fdwAccess,BOOL bInheritHandle,PCTSTR pszName);获得它自己的进程与现有信标相关的句柄
lMaxmumCount参数用于告诉系统,应用程序处理的最大资源数量是多少。lInitialCount参数用于指明开始时(当前)这些资源中有多少可供使用。
通过调用等待函数,传递负责保护资源的信标的句柄,线程就能够获得对该资源的访问权。从内部来说,该等待函数要检查信标的当前资源数量,如果它的值大于0(信标已经发出信号) ,那么计数器递减1,调用线程保持可调度状态。信标的出色之处在于它们能够以原子操作方式来执行测试和设置操作,这就是说,当向信标申请一个资源时,操作系统就要检查是否有这个资源可供使用,同时将可用资源的数量递减,而不让另一个线程加以干扰。只有当资源数量递减后,系统才允许另一个线程申请对资源的访问权。
BOOL ReleaseSemaphore(HANDLE hsem,LONG lReleaseCount,PLONG plPreviousCount);
该函数只是将lReleaseCount中的值添加给信标的当前资源数量。该函数也能够在它的*plPreviousCount中返回当前资源数量的原始值。
9.6   互斥对象内核对象
互斥对象(mutex)内核对象能够确保线程拥有对单个资源的互斥访问权。互斥对象包含一个使用数量,一个线程I D和一个递归计数器。互斥对象的行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。ID用于标识系统中的哪个线程当前拥有互斥对象,递归计数器用于指明该线程拥有互斥对象的次数。通常来说,它们用于保护由多
个线程访问的内存块。
互斥对象的使用规则如下:
• 如果线程I D是0(这是个无效I D) ,互斥对象不被任何线程所拥有,并且发出该互斥对象的通知信号。
• 如果I D是个非0数字,那么一个线程就拥有互斥对象,并且不发出该互斥对象的通知信号。
• 与所有其他内核对象不同, 互斥对象在操作系统中拥有特殊的代码,允许它们违反正常的规则 。
HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa,BOOL fInitialOwner,PCTSTR pszName);
HANDLE OpenMutex(DWORD fdwAccess,BOOL bInheritHandle,PCTSTR pszName);
fInitialOwner参数用于控制互斥对象的初始状态。如果传递 FA L S E(这是通常情况下传递的值) ,那么互斥对象的I D和递归计数器均被设置为0。这意味着该互斥对象没有被任何线程所拥有,因此要发出它的通知信号。如果为fInitialOwner参数传递T R U E,那么该对象的线程I D被设置为调用线程的I D,递归计数器被设置为1。由于I D是个非0数字,因此该互斥对象开始时不发出通知信号。
通过调用一个等待函数,并传递负责保护资源的互斥对象的句柄,线程就能够获得对共享资源的访问权。在内部,等待函数要检查线程的 I D,以了解它是否是0(互斥对象发出通知信
号) 。如果线程I D是0,那么该线程I D被设置为调用线程的I D,递归计数器被设置为1,同时,调用线程保持可调度状态。如果等待函数发现I D不是0(不发出互斥对象的通知信号) ,那么调用线程便进入等待状态。系统将记住这个情况,并且在互斥对象的 I D重新设置为0时,将线程I D设置为等待线程的I D,将递归计数器设置为1,并且允许等待线程再次成为可调度线程。
BOOL ReleaseMutex(HANDLE hMutex);释放互斥对象,该函数将对象的递归计数器递减 1

9.8   其他的线程同步函数
9.8.1   异步设备I / O
异步设备I / O使得线程能够启动一个读操作或写操作,但是不必等待读操作或写操作完成。
设备对象是可以同步的内核对象,这意味着可以调用WaitForSingleObject函数,传递文件、套接字和通信端口的句柄。当系统执行异步I / O时,设备对象处于未通知状态。一旦操作完成,系统就将对象的状态改为已通知状态,这样,该线程就知道操作已经完成。此时,该线程就可以继续运行。
线程也可以调用WaitForInputIdle来终止自己的运行
DWORD WaitForInputIdle(HANDLE hProcess,DWORD dwMilliseconds);
该函数将一直处于等待状态,直到hProcess标识的进程在创建应用程序的第一个窗口的线程中已经没有尚未处理的输入为止。当父进程的线程调用CreateProcess时,该父进程的线程将在子进程初始化时继续运行。父进程的线程可能需要获得子进程创建的窗口的句柄。如果父进程的线程想要知道子进程何时完成初始化,唯一的办法是等待,直到子进程不再处理任何输入为止。因此,当调用CreateProcess后,父进程的线程就调用WaitForInputIdle。
编写1 6位Windwos应用程序的编程人员常常要面对这个问题。应用程序想要将消息显示在窗口中,但是它并不确切知道窗口何时创建完成、作好接受消息的准备。
9.8.3  MsgWaitForMultipleObjects( Ex )
此函数用于线程等待它自己的消息。
DWORD MsgWaitForMultipleObjects(DWORD dwCount,PHANDLE phObjects,BOOL fWaitAll,DWORD dwMilliseconds,DWORD dwWakeMask);
DWORD MsgWaitForMultipleObjectsEx(DWORD dwCount,PHANDLE phObjects,BOOL fWaitAll,DWORD dwWakeMask,DWORD dwFlags);
这些函数与WaitForMultipleObjects函数十分相似。差别在于它们允许线程在内核对象变成已通知状态或窗口消息需要调度到调用线程创建的窗口中时被调度。
创建窗口和执行与用户界面相关的任务的线程,应该调用MsgWaitForMultipleObjectsEx函数,而不应该调用WaitForMultipleObjects函数,因为后面这个函数将使线程的用户界面无法对用户作出响应。
9.8.5 SingleObjectAndWait
SingleObjectAndWait函数用于在单个原子方式的操作中发出关于内核对象的通知并等待另一个内核对象。
DWORD SingalObjectAndWait(HANDLE hObjectToSignal,HANLDE hObjectToWaitOn,DWORD dwMilliseconds,BOOL fAlertable);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值