这一篇还是讨论多线程的问题,在这一篇中我们使用事件对象来解决同样的问题(火车站售票),还是先看我们需要用到的三个函数的原型:
(1)CreateEvent()创建事件对象
HANDLE
WINAPI
CreateEvent(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_ BOOL bManualReset,
_In_ BOOL bInitialState,
_In_opt_ LPCWSTR lpName
);
lpEventAttributes
一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
bManualReset
指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState
指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName
指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。
如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。
如果lpName为NULL,将创建一个无名的事件对象。
如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。
(2)SetEvent()设置事件对象状态
BOOL
WINAPI
SetEvent(
_In_ HANDLE hEvent
);
hEvent
指定将要设置其状态的时间对象的句柄
(3)ResetEvent()重置事件对象状态
BOOL
WINAPI
ResetEvent(
_In_ HANDLE hEvent
);
hEvent
指定将要重置其状态的事件对象的句柄。
那么如何利用事件对象解决线程同步的问题呢?上代码:
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun31Proc(LPVOID lpParameter);
DWORD WINAPI Fun32Proc(LPVOID lpParameter);
//int index=0;
int tickets3 = 100;
HANDLE g_hEvent;
void main003(){
HANDLE hThread1;
HANDLE hThread2;
//创建自动重置事件内核对象
g_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
//设置为有信号状态
SetEvent(g_hEvent);
//创建线程
hThread1 = CreateThread(NULL, 0, Fun31Proc, NULL, 0, NULL);
hThread2 = CreateThread(NULL, 0, Fun32Proc, NULL, 0, NULL);
//关闭句柄,函数并没有终止新创建的线程,只是表示在主线程中对新创建的线程的引用不感兴趣,因此将它关闭
CloseHandle(hThread1);
CloseHandle(hThread2);
//main 函数主线程等待4秒
Sleep(1000);
//关闭事件对象
CloseHandle(g_hEvent);
system("pause");
return;
}
//线程1的入口函数
DWORD WINAPI Fun31Proc(LPVOID lpParameter){
while (true)
{
//所请求的对象属于处于有信号状态,该函数才会返回,线程才能继续往下执行
WaitForSingleObject(g_hEvent, INFINITE);
ResetEvent(g_hEvent);
if (tickets3 > 0){
cout << "thread1 sell ticket" << tickets3-- << endl;
//释放当前线程对互斥对象的所有权,让该对象处于已通知状态
SetEvent(g_hEvent);
}
else{
SetEvent(g_hEvent);
break;
}
}
return 0;
}
DWORD WINAPI Fun32Proc(LPVOID lpParameter){
while (true)
{
//所请求的对象属于处于有信号状态,该函数才会返回,线程才能继续往下执行,注意调用WaitForSingleObject()的位置
WaitForSingleObject(g_hEvent, INFINITE);
ResetEvent(g_hEvent);
if (tickets3 > 0){
cout << "thread2 sell ticket" << tickets3-- << endl;
//释放当前线程对互斥对象的所有权,让该对象处于已通知状态,注意调用WaitForSingleObject()的位置
SetEvent(g_hEvent);
}
else{
//释放当前线程对互斥对象的所有权,让该对象处于已通知状态,注意调用WaitForSingleObject()的位置
SetEvent(g_hEvent);
break;
}
}
return 0;
}
在这个事件对象处理多线程同步的问题中,我们需要注意:事件对象有人工重置事件对象和自动重置事件对象。当人工重置事件对象得到通知时,等待该事件的所有线程都变为可调度线程;而对于自动重置事件对象得到通知时,只有一个线程变为可调度的线程,同时操作系统会将该事件对象设置无信号状态,这样,当对所保护的代码执行完成后,需要调用SetEvent函数将该事件对象设置为有信号状态。而人工重置的事件对象,在一个线程得到该事件对象之后,操作系统并不会将该事件对象设置为无信号状态,除非显式地调用ResetEvent函数将其设置为无信号状态,否则该对象会一直是有信号状态。