//创建事件
HANDLECreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,
BOOLbManualReset,
BOOLbInitialState,
LPCTSTRlpName
);
函数说明:
第一个参数表示安全控制,一般直接传入NULL。
第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。
第三个参数表示事件的初始状态,传入TRUR表示已触发。
第四个参数表示事件的名称,传入NULL表示匿名事件。
//根据名称获得一个事件句柄
HANDLEOpenEvent(
DWORDdwDesiredAccess,
BOOLbInheritHandle,
LPCTSTRlpName //名称
);
//每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态
BOOLSetEvent(HANDLEhEvent);
//将事件设为末触发
BOOLResetEvent(HANDLEhEvent);
//事件的清理与销毁
CloseHandle()
为了解决多线程问题,我们设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。详见代码:
#include <stdio.h>
#include <process.h>
#include <windows.h>
long lGlobVar = 0;//全局资源
//事件与关键段
HANDLE hEventPtrParam;
CRITICAL_SECTION csGlobVar;
unsigned int WINAPI ThreadProc(void* lpParam){
//由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来
int iThreadNo = *(int*)lpParam;
SetEvent(hEventPtrParam);//触发事件
Sleep(50);//Do Sth
EnterCriticalSection(&csGlobVar);
lGlobVar++;//处理全局资源
Sleep(50);//Do Sth
printf("线程编号:%d,全局变量:%d.\n", iThreadNo, lGlobVar);
LeaveCriticalSection(&csGlobVar);
return 0;
}
void main()
{
//初始化事件和关键段 自动置位,初始无触发的匿名事件
hEventPtrParam = CreateEventW(NULL, FALSE, FALSE, NULL);
InitializeCriticalSection(&csGlobVar);
HANDLE hThreads[10];
for(int i = 0; i < 10; i++){//等子线程接收到参数时主线程可能改变了这个i的值
hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, &i, 0, NULL);//创建线程
WaitForSingleObject(hEventPtrParam, INFINITE);//等待事件被触发,等待到复位后,立马置位
}
//保证子线程已全部运行结束
WaitForMultipleObjects(10, hThreads, TRUE, INFINITE);
for(int i = 0; i < 10; i++){
CloseHandle(hThreads[i]);//关闭线程句柄,使其内核对象计数减一
}
//销毁事件和关键段
CloseHandle(hEventPtrParam);
DeleteCriticalSection(&csGlobVar);
}
可以看出来,经典线线程同步问题已经圆满的解决了——线程编号的输出没有重复,说明主线程与子线程达到了同步。全局资源的输出是递增的,说明各子线程已经互斥的访问和输出该全局资源。