三、内核模式下的线程同步
- Windows系统中有多种机制可用于线程同步,它们一般都被称之为内核对象(并非全部),一般我们常用的有以下几种:
- 互斥对象(Mutex)
- 事件对象(Event)
- 信号量(Semaphore)
- 可等待计时器(Waitable Timer)
0.等待函数
- WaitForSingleObject
等待函数的作用是使一个线程进入到等待状态,直到指定的内核对象被触发为止,其函数原型如下所示:
DWORD WaitForSingleObject(
_In_ HANDLE hHandle, //内核对象句柄
_In_ DWORD dwMiliseconds //等待超时时间(微秒,INFI)
);
/******
***return
WAIT_OBJECT_0: 成功返回
WAIT_TIMEOUT: 超时返回
WAIT_FAILED: 传入的参数错误
*/
在创建线程时使用等待函数后,此函数会在等待超时或线程结束时返回,因此我们的主线程一次只能启动一个线程,从而避免上述例子中多线程访问同一个数据时所引发的问题.
- WaitForMultipleObjects
与WaitForSingleObject类似,唯一的不同之处在于它允许调用线程同时检查多个内核对象的触发状态
DWORD WaitForMultipleObjects(
DWORD dwCount, // 检查的内核对象的数量
CONST HANDLE* phObjects, // 内核对象句柄数组
BOOL bWaitAll, // 是否在所有内核对象触发之后返回
DWORD dwMilliseconds) // 等待时间
/***********
* return
WAIT_FAILED
WAIT_TIMEOUT
// 如果 bWaitAll 是 TRUE ,则返回 WAIT_OBJECT_0
// 如果 bWaitAll 是 FALSE, 则返回值是 WAIT_OBJECT_0和(WAIT_OBJECT_0 + dwCount - 1) 之间的任意一个值,内核对象数组的下标为:返回值 - WAIT_OBJECT_0
*/
对一些内核对象来说,成功地调用 WaitForSingleObject 与 WaitForMultipleObjects 事实上会改变对象的状态。如:自动重置事件内核对象,当时间对象被触发的时候,函数会检测到这一情况,这时它可以直接返回 WAIT_OBJECT_0 给调用线程。但是,就在函数返回之前,它会使事件变为非触发状态——这就是等待成功所引起的副作用。 WaitForMultipleObjects是以原子方式工作的,当函数检查内核对象的状态时,任何其他线程都不能在背后修改对象的状态。
1.事件对象
事件(Event) 是在线程同步中最常使用的一种同步对象,而且比其他对象要简单一些。像其他对象一样,事件包含了一个使用计数,一个是用来标识自动重置/手动重置的BOOL值,另一个是表示事件有没有触发的BOOL值
- 事件对象有两种状态,他们分别是:
- 手动状态:被触发后所有等待该事件的线程都将变为可调度状态,常用于控制具有较强自定义要求的多线程同步环境.
- 自动状态:被触发后只有一个等待事件线程会变成可调度状态。
/*关键函数*/
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,//属性
BOOL hManualReset, //手工重置
BOOL bInitialState, //初始状态
LPCTSTR lpName //事件对象名称
);
//注:非手工状态下,调用SetEvent放行一个线程后,会自动再次设为无
//信号状态,直到再次调用SetEvent
/*设置标记为有信号状态(释放等待函数)*/
BOOL SetEvent(
HANDLE hEvent //事件对象句柄
);
/*重置标记为无信号状态(阻塞等待函数)*/
BOOL WINAPI ResetEvent(
HANDLE hEvent //事件对象句柄
);
//打开内核对象
HANDLE OpenEvent(
DWORD dwDesiredAccess,//对事件对象的请求访问权限
BOOL bInheritHandle,//是否能继承
LPCTSTR lpName //事件对象的名字
);
一个防多开的例子
if (!OpenEvent(EVENT_MODIFY_STATE, TRUE,L"Global\\Text"))
CreateEvent(NULL, TRUE, TRUE, L"Global\\Text");
else
return 0;
示例:
int g_nNum = 0;
HANDLE g_hEventA = nullptr;
HANDLE g_hEventB = nullptr;
DWORD WINAPI ThreadProcA(LPVOID lpParam) {
for (int i = 0; i <