用户同步的方式的优点是速度非常快。
但也有局限性:互锁函数只能在单值上运行,无法使线程进入等待状态。关键代码段函数
可以使线程等待,但只能对单个进程中的线程同步,且容易产生死锁(进入关键代码段时
不能设置超时值)。
内核对象同步方式的缺点:速度慢,当调用任何内核同步函数时,调用线程都会从用户方式
切换到内核方式。
内核对象同步状态:已通知、未通知。
eg.进程:进程创建时总是处于未,进程运行时处于未,进程终止运西时处于已。(这些状态
是由内核对象中一个布尔值控制的)。
因此如果想知道进程现在的状态,只需查看该布尔值即可。这需要调用一个API,由OS进行
查询。
可查询状态的内核对象:
进程、线程、作业、文件、控制台输入、
文件修改通知、事件、可等待定时器、信标、互斥对象
等待函数
使线程自愿进入等待状态,知道一个特定内核对象变为已通知状态。
DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds)
当线程调用该函数时,第一个参数hObject标识一个支持能够被通知/通知的内核对西,
第二个参数指明为了等待对象变为已通知,它需要等待多久。
WaitForSingleObject(hProcess, INFINITE);告诉OS,调用函数等待到进程终止为止。
第二个参数不能传0,否则函数会直接返回。
返回值:WAIT_OBJECT_0,等待对象变为已通知。WAIT_TIMEOUT,超时。WAIT_FAILED,出错。
DWORD WaitForMultipleObjects(DWORD dwCount, CONST HANDLE *phObjects,
BOOL fWaitAll, DWORD dwMilliseconds)
同时查看若干内核对象。dwCount为要查看内核对象的数量,值在1到MAXIMUM_WAIT_OBJECTS(64)
之间,phObjects为内核对象指针数组。
fWaitAll如果TRUE,则必须等待所有内核对象已通知,若FALSE,只需等待一个变为已通知即可。
返回值:如果fWaitAll==TRUE,所有内核对象都已通知时,返回WAIT_OBJECT0;
如果fWaitAll==FALSE,返回值不是WAIT_TIMEOUT and WAIT_FAILED,就表示有一个内核对象
已变为已通知,它的下标是(返回值 - WAIT_OBJECT_0)。
成功等待的副作用
若等待成功会对一个对象状态改变,称之成功等待的副作用(一般为自动重置内核对象)。不同
内核对象副作用不同,有些则根本没有副作用,如进程、线程。
多个线程等待一个内核对象时,对象已通知时,大家被唤醒的机会均等(只是都有机会而已,
不一定真正均等)。
事件内核对象
组成部分:引用计数,自动重置抑或人工重置事件的布尔值,事件已通知或未通知的布尔值。
自动重置事件:得到通知时,等待该事件的所有线程均变为可调度状态。
人工重置事件:得到通知时,只有一个等待该事件的线程变为可调度状态。
HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa, // 安全属性
BOOL fManualReset, // 人工重置事件(TRUE) or 自动重置事件(FALSE)
BOOL bInitialState, // 初始状态
PCTSTR pszName) // 命名对象名
HANDLE OpenEvent(DWORD fdwAccess, BOOL fInherit, PCTSTR pszName);//CloseHandle
改变事件状态:SetEvent(HANDLE)//已通知;ResetEvent(HANDLE)//设为未通知态
对于自动重置事件,当线程成功等待到该对象时,自动重置的事件就会设置到未通知状态。
(即副作用规则)。然而,对人工重置事件没有这个副作用规则,即该对象状态仍保持已通知。
另外一个Event函数,BOOL PulseEvent(HANDLE hEvent),它使变为已通知状态,然后立即
又变为未通知状态。慎用。
等待计时器内核对象
在某个时间或按特定时间间隔发出信号通知内核对象。
HANDLE CreateWaitableTimer
(PSECURITY_ATTRIBUTES psa, BOOL fManualReset, PCTSTR pszName)
fManualReset指明为人工重置还是自动重置。当发出人工重置的定时器信号通知时,等待
定时器的所有线程都变为可调度;当发出自动重置定时器信号时,只有一个等待的线程
变为可调度状态。
进程获得与自己相关的现有等待定时器句柄:
HANDLE OpenWaitableTimer(DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName)
设置定时器已通知状态的API:
BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER *pDueTime,//何时第一次报时
LONG lPeriod, //间隔多长事件再报时
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArggToCompletionRoutine, BOOL fResume);
eg. how to use Waitable Timer.
//
HANDLE hTimer;
SYSTEMTIME st;
FILETIME ftLocal, ftUTC;
LARGER_INTEGER liUTC;
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);//建一个自动重置等待计时器
//init st
st.wYear = 2007;
//...
SystemTimeToFileTime(&st, &ftLocal);// system time to local time
LocalFileTimeToFileTime(&ftLocal, &ftUTC);//local time to UTC time
liUTC.LowPart = ftUTC.dwLowDateTime; // FILETIME to LARGE_INTEGER
liUTC.highPart = ftUTC.dwHighDateTime;
// Set the timer
SetWaitableTimer(hTimer, &liuTC, 6 * 60 * 60 * 1000,//每间隔6小时报时
NULL, NULL, FALSE);
//...
//一些说明
现对st初始化,设置第一次报时时间(发信号通知)。
FILETIME与LARGER_INTEGER地址结构采用相同的二进制格式,但结构的调整要求不同。
FILETIME必须以一个32位边界开始,而LARGER_INTEGER必须以一个64位的边界开始。
但是,x86的机型能够悄悄处理数据的对其,将FILETIME与LARGER_INTEGER对其,其它
机型则不一定,因此SetWaitableTimer的第二个参数如果传(PLARGE_INTEGER)&ftUTC
是不负责任的。
SetWaitableTimer的其它说明:
//可以不设置liuTC的绝对时间,只需其为一个负值(相对100ns),
其相对SetWaitableTimer第一次报时。
如:liuUTC.QuadPart = -(5 * 10 000 000)//5s后第一次报时。
注:1s = 1000 ms = 1 000 000 us = 1 000 000 000 ns。
如果lPeriod传0,表示只报时一次。
最后一个参数,一般传FALSE,但如果传TRUE,表示支持暂停的计算机,它将使计算机
摆脱暂停状态(如果是的话)。
BOOL CancelWaitableTimer(HANDLE hTimer);
撤销定时器,可以调用SetWaitableTimer重新设置定时器。
定时器给APC排队
APC,异步过程调用。
SetWaitableTimer后三个参数中前两个的意义:APC例程函数地址及参数。
APC例程形式:
VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine,
DWORD dwTimerLowValue, DWORD dwTimerHighValue);
该函数可以在定时器报时的时候由调用SetWaitableTimer函数的同一线程来调用,但是只有
在调用线程在等待状态下才能调用。即该线程必须正在下列函数调用中等待:
SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx、
MsgWaitForMultipleObjectesEx、SingleObjectAndWait。
若线程不在其中等待,系统不会为定时器APC例程排队。以防止线程的APC队列里塞满
定时器APC通知,浪费内存。
定时器报时的时候,如果线程处于等待中,就会调用APC例程。
线程不该等待定时器句柄,也不该以待命方式等待定时器。
定时器的松散特性
当不得已创建了很多定时器对象时,可以考虑使用CreateTimerQueueTimer替换。
应该避免使用定时器APC排队。
用户定时器能够生成WM_TIMER消息,这些消息将返回给调用 SetTimer(用于回调定时
器)的线程和创建窗口(用于基于窗口的定时器)的线程。因此,当用户定时器报时的时候,
只有一个线程得到通知。另一方面,多个线程可以在等待定时器上进行等待,如果定时器是个
人工重置的定时器,则可以调度若干个线程。
信标
信标内核对象用于资源进行计数。它所包含的内容有:它本身的引用计数、最大资源数量、
当期资源数量。
信标使用规则:当前资源数量大于0时发出信标信号;等于0时不发信号;不允许为负;
不允许大于最大资源数量。
要十分注意不能将信标对象的使用数量与其当前资源数量混淆。
创建信标:
HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa, //安全属性
LONG lInitialCount,//资源出示值
LONG lMaximumCount, //资源最大数量
PCTSTR pszName)//命名
其它进程打开现有信标句柄
HANDLE OpenSemaphore(DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName)
信标的测试和设置操作是按原子方式进行的。即当信标申请一个资源时,OS会检查该资源的
使用情况,完成后,资源数量递减。
如果等待函数确定信标当前资源数量是零,那么系统就调用函数进入等待状态。当另一
线程对信标的当前资源数量进行递增时,系统会记住该等待线程,允许其变为可调度状态。
递增资源数量的API
BOOL ReleaseSemaphore(HANDLE hsem, LONG lReleaseCount,PLONG plPreviousCount)
互斥对象内核对象
保证线程拥有对单个资源的护斥访问权。它的内容包括:本身引用计数、一个线程ID、
一个递归计数器。其特性与关键代码段相同,速度会慢,但可以实现不同进程中的多个
线程访问单个互斥对象,并且可以在线程等待访问资源时设置超时值。
线程ID用于标识OS中那个线程当前拥有互斥对象,递归计数器用于标记该线程拥有互斥对象
的次数。
互斥对象使用规则:线程ID=0,互斥对象不为任何线程所有,发出通知信号;ID!=0,一个
线程拥有互斥对象,不发出互斥信号通知事件;异常。
HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa, BOOL fInitialOwner, PCTSTR pszName)
fInitialOwner=FALSE表明互斥对象ID与递归计数器均置0。fInitialOwner=TRUE表示线程
ID设为调用调用线程的ID,递归计数设0。
HANDLE OpenMutex(DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName)
释放互斥内核对象,函数将递减对象的递归计数器。
BOOL ReleaseMutex(HANDLE hMutex)调用线程将查看本线程ID与互斥内核对象ID是否一致,
是则释放,否则返回FALSE,么都不做。
其它线程同步函数:
DWORD WaitForInputIdle(HANDLE hProcess, DWORD dwMilliseconds)
使线程等待,直到hProcess标识的进程在创建应用程序的第一个窗口的线程中已经没有
尚未处理的输入为止。
MsgWaitForMultipleObjects、MsgWaitForMultipleObjectsEx与WaitForMultipleObjects
类似,主要区别在于前者允许线程在内核对象变成已通知状态或窗口消息需要调度到调用
线程创建的窗口中时被调度。
WaitForDebugEvent:
当调试程序调用该函数时,调试程序的线程终止运行,系统将调试事件已经发生的情况通
知调试程序,方法是允许调用的该函数返回。pde参数指向的结构在唤醒调试
程序的线程之前由系统填入信息。
SingleObjectAndWait:
用于在单个原子方式的操作中发出关于内核对象的通知并等待另一个内核对象。