## 事件对象
event对象常用来多个线程间进行工作的同步,如线程A先执行一些初始化工作,触发evnet,通知线程B初始化工作已经完成,可以进行接下来的工作。
### 创建event对象
~~~
HANDLE WINAPI CreateEvent(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, // 设置安全属性
_In_ BOOL bManualReset, // 是否人工重置状态(人工重置则不会自动改变事件状态, 自动重置则会自动将事件恢复为未触发)
_In_ BOOL bInitialState, // 事件初始状态(触发/未触发)
_In_opt_ LPCTSTR lpName // 事件名称
);
~~~
值得注意的是
~~~
<span style="color:#FF0000;">BOOL bManualReset, </span>
~~~
若 为人工重置,那么当事件触发时,所有等待线程均能够获得事件对象,且不会自动重置事件状态。若为自动重置,则仅有一个线程wait获得该事件,同时置事件为未触发状态。
另外一点,其他线程若想获得该事件对象句柄,可以也调用CreateEvent函数,并传入事件名称。若该事件已经存在,则直接返回句柄,若未存在则会创建该事件并返回句柄。
注意,若事件已经存在,再调用CreateEvent只会获取其句柄,但该函数的其他参数会忽略。
对于自动重置事件,若multiplewait函数为全部等待状态,则对于仅自动重置事件触发时,multiplewait函数会忽略该event,同时不会重置事件,只有当所有的等待对象都触发时,multiplewait才会获取自动重置事件并自动重置为未触发状态。
**若想在创建事件时指定的可以访问事件的权限,可以用**
~~~
HANDLE WINAPI CreateEventEx(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_opt_ LPCTSTR lpName,
_In_ DWORD dwFlags, // 可以是两种flags的任意组合<strong>CREATE_EVENT_INITIAL_SET</strong>、<strong>CREATE_EVENT_MANUAL_RESET</strong>
_In_ DWORD dwDesiredAccess // 设置事件权限
);
~~~
### 获取事件句柄函数
~~~
HANDLE WINAPI OpenEvent(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCTSTR lpName
);
~~~
### 改变事件触发状态
### 设置事件为触发状态
~~~
BOOL WINAPI SetEvent(
_In_ HANDLE hEvent
);
~~~
### 设置事件未触发状态
~~~
BOOL WINAPI ResetEvent(
_In_ HANDLE hEvent
);
~~~
## 可等待计时器内核对象
可等待计时器对象会在一定时后或每间隔一段时间触发,可用在某个时间的操作。
### 创建或获取可等待计时器
~~~
HANDLE WINAPI CreateWaitableTimer(
_In_opt_ LPSECURITY_ATTRIBUTES lpTimerAttributes,
_In_ BOOL bManualReset, // 是否人工重置
_In_opt_ LPCTSTR lpTimerName
);
~~~
### 获取可等待计时器句柄
~~~
HANDLE WINAPI OpenWaitableTimer(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCTSTR lpTimerName
);
~~~
不像事件对象,可等待计时器创建后总是未触发的。
需要调用函数 SetWaitableTimer
~~~
BOOL WINAPI SetWaitableTimer(
_In_ HANDLE hTimer, // 计时等待对象
_In_ const LARGE_INTEGER *pDueTime, // 何时触发对象(用负值表示相对于调用SetWaitableTimer后的时间 100纳秒为单位)
_In_ LONG lPeriod, // 触发后间隔的触发频率(0 表示仅触发一次)
_In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine, // APC调用函数
_In_opt_ LPVOID lpArgToCompletionRoutine, // APC调用参数
_In_ BOOL fResume // 在可挂起的计算机系统中,是否恢复计算机来使等待线程执行CPU时间。
// 若传入FALSE,则会触发对象,但等待线程不会执行,除非直到计算机系统重新执行
);
~~~
### 取消计时等待对象的时间设置
~~~
该函数会取消一切的SetWaitableTimer的计时设置。
<strong>但是该函数不会更改timer对象的触发状态</strong>,若已经触发,则该对象仍会处于触发状态。
BOOL WINAPI CancelWaitableTimer(
_In_ HANDLE hTimer
);
~~~
### 计时等待对象 VS 用户计时器(SetTimer)
1、内核对象,用户对象
2、用户计时器会产生WM_TIMER消息,该消息会被送到调用SetTimer线程或创建窗口线程,同一时间仅有一个线程得到通知。
计时等待对象可多个线程同时被通知。
## 信号量内核对象
信号能够灵活的限制可被激活的线程数目,并确保线程数目不会超过设定的最大值。
具体使用流程为:
1、创建信号量对象,并指定最大资源数目与当前可用数目(常为0)。
2、创建多个资源请求线程,因为当前可用数目为0,线程等待。
3、当符合某种条件时,调用[**ReleaseSemaphore**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685071%28v=vs.85%29.aspx)函数释放资源,这时候可用资源数目递增。
4、可以资源数目不再为0,等待线程获得资源,同时可以资源数目递减。
windows系统会确保当前可用资源数目大于等于0,同时不会超过最大值。
### 创建(或获取)信号量对象
~~~
HANDLE WINAPI CreateSemaphore(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
_In_ LONG lInitialCount, // 初始当前可用资源数目
_In_ LONG lMaximumCount, // 最大可用资源数目
_In_opt_ LPCTSTR lpName
);
~~~
[**CreateSemaphoreEx**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682446%28v=vs.85%29.aspx)
### 获取信号量对象句柄
~~~
HANDLE WINAPI OpenSemaphore(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCTSTR lpName
);
~~~
### 递增信号量可用资源
~~~
BOOL WINAPI ReleaseSemaphore(
_In_ HANDLE hSemaphore,
_In_ LONG lReleaseCount,
_Out_opt_ LPLONG lpPreviousCount
);
~~~
## 互斥量内核对象
互斥量内核对象用于确保资源被唯一的线程访问,即互斥访问。
### 创建(获取)互斥量对象
~~~
HANDLE WINAPI CreateMutex(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_ BOOL bInitialOwner,
_In_opt_ LPCTSTR lpName
);
~~~
互斥量内核对象 有 引用计数器,线程ID已经递归计数器组成。
线程ID用来记录当前获取互斥量对象的线程ID,0表示没人获取,互斥量处于触发状态。一旦,有个线程wait到互斥量,其内核对象线程ID为该线程ID,同时内核对象变为未触发状态,其他线程只能继续等待。但对于已经获得互斥量线程,其仍可以等待成功,这时候内核对象会递增其递归计数器。
调用[**ReleaseMutex**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685066%28v=vs.85%29.aspx) 释放互斥量。对于多次递归进入的互斥量,要相应的多次调用release函数。
注意,当线程在获取了互斥量对象,而在调用[**ReleaseMutex**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685066%28v=vs.85%29.aspx)之前结束的话,会产生遗弃问题。
## 关于内核态同步对象的一些事项
1、一般的,通过内核对象来进行同步,其获取的内核对象句柄都是具有所有权限的(访问,改变触发状态等),但是我们可以在Create内核对象时,通过扩展函数ex函数设置可访问权限,那么当访问该内核对象句柄时,仅能够进行指定的权限访问。
2、内核对象命名与多用户系统
我们有多种方法可以在多个进程空间访问同一个内核对象(继承,dumplicatehandle,命名的内核对象)。
在使用命名内核对象访问时,需要注意在多用户系统中内核对象名称的前缀。
MSDN原话是:
The name can have a "Global\" or "Local\" prefix to explicitly create the object in the global or session namespace.
即加上“Global\”前缀,可以在多个用户间通过名称访问该内核对象,而"Local\"前缀仅能够当前用户通过名称访问内核对象。