目录
一、前言
二、基础用法
三、API详解
1.创建事件对象
2控制事件状态
3.等待事件对象:
四、实战案例
1.案例描述
2.代码设计
3.总设计代码
4.运行结果
一、前言
事件对象(Event Object)是我们在大型项目中,进行多线程同步处理的时候经常用到的一种内核对象,下面我就根据它的基础本身的特点和相关的API函数,与实战案例相结合,讲述它的基础理论和用法。
二、基础用法
在Windows编程中,事件对象(Event Objects)是一种内核对象,主要用于线程之间的同步。当多个进程需要访问共享资源时,可以通过CreateEvent创建的事件对象来控制访问顺序,避免资源冲突和数据不一致的问题。
事件对象中经常结合进行使用的有以下四种api函数,我们掌握了这四种API函数的基本用法,可以说就掌握了事件对象(Event Object)。以下api函数分别为 CreateEvent , SetEvent,
ResetEvent 和 WaitForSingleObject。后面我会依次讲解各个api函数的原型以及作用。
三、API详解
1.创建事件对象
我们可以使用 CreateEven 函数来创建一个事件对象,它是一个Windows API函数,这个函数允许你指定事件对象的安全属性、是手动重置还是自动重置、以及它的初始状态(信号态或非信号态)。下面是它的原型:
HANDLE WINAPI CreateEventW(_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性_In_ BOOL bManualReset, // 复位方式:true 必须用 resetevent手动复原 false 自动还原为无信号状态_In_ BOOL bInitialState, //初始状态 : true 初始状态为有信号状态: false 无信号状态_In_opt_ LPCWSTR lpName //对象名称: null 无名的事件对象);
可以根据原型解释,它的返回值类型为 句柄(空指针) ,函数约束类型为 WINAPI (_stdcall)。
第一个参数为 lpEventAttributes ,也就是安全属性,它是作为windows内核对象必须要有的参数类型。
第二个参数是指复位方式,如果为TRUE,则事件对象需要显式调用ResetEvent函数来重置为无信号状态;如果为FALSE,则事件对象在单个等待线程被释放后自动重置为无信号状态。
第三个参数为信号状态也就是指定事件对象的初始状态,如果为TRUE,则事件对象被创建时处于有信号状态;如果为FALSE,则处于无信号状态。
第四个参数为指定事件对象的名称,通常为NULL,为无名的事件对象。
2控制事件状态
事件对象有两种状态——发信号和不发信号。
SetEvent:将事件对象的状态置为发信号状态,允许等待该事件的线程继续执行。
ResetEvent:将事件对象的状态置为不发信号状态。
WINBASEAPI BOOL WINAPI SetEvent(_In_ HANDLE hEvent);WINBASEAPI BOOL WINAPI ResetEvent(_In_ HANDLE hEvent);
原型为上述代码,参数都是为 HANDLE (句柄),也就是 CreateEven 函数的返回值,
3.等待事件对象:
使用 WaitForSingleObject 或 WaitForMultipleObjects 函数等待一个或多个事件对象变为信号态, 线程才会继续向下执行。
函数原型如下,参数都大致相同。
WINBASEAPI DWORD WINAPI WaitForSingleObject(_In_ HANDLE hHandle,_In_ DWORD dwMilliseconds);
四、实战案例
上面我们已经讲述了事件对象的作用以及一些常用的api方法和属性,下面我将会通过一个实际有代表性的案例来继续讲解事件对象,来加深它的用法和印象。
1.案例描述
下面游乐园有两个售票口 A 和 售票口 B,游乐园限制最多100人进,假设这两个售票口所卖的是不同种类的票,一共有100张。那么该如何设计程序,保证售票口 A 和 售票口 B 同时所卖的票不是同一张票。
2.代码设计
上述案例我们可以用编程的角度去分析问题和解决问题。
售票口 A 和 售票口 B 可以分别看作两个线程,线程 A和线程 B。100张票可以当作全局变量,作为线程A,B需要访问的公共资源。代码设计为:
#include <stdio.h>
#include <windows.h>
#include <process.h>// 共享资源 (100张票)
int iTickets = 100;// 事件对象
HANDLE g_hEvent;int main()
{// 线程A 和 线程BHANDLE hThreadA, hThreadB;hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, 0);hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, 0);CloseHandle(hThreadA); CloseHandle(hThreadB);system("pause");return 0;
}
那么就只需要保证线程A 和 线程 B在同一时间只能对共享资源的单一访问,这里我们就可以用到事件对象(Event Objects)。
//手动重置 FALSE:设置无信号状态,未触发状态g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);SetEvent(g_hEvent);Sleep(4000);CloseHandle(g_hEvent);
具体做法为 进程A 可以通过 SetEvent函数 将事件对象的状态设置为有信号状态,进程B 则可以通 WaitForSingleObject 等函数等待该事件对象变为有信号状态,从而实现进程间的信号传递和协调,代码为:
while (1){WaitForSingleObject(g_hEvent, INFINITE);if (iTickets > 0){Sleep(1);iTickets--;printf("A remain %d\n",iTickets);}else{break;}SetEvent(g_hEvent);}return 0;
3.总设计代码
以下是设计的总代码:
#include <stdio.h>
#include <windows.h>
#include <process.h>// 共享资源 (100张票)
int iTickets = 100;// 事件对象
HANDLE g_hEvent;DWORD WINAPI SellTicketA(void* arg)
{while (1){WaitForSingleObject(g_hEvent, INFINITE);if (iTickets > 0){Sleep(1);iTickets--;printf("A remain %d\n",iTickets);}else{break;}SetEvent(g_hEvent);}return 0;
}
DWORD WINAPI SellTicketB(void* arg)
{while (1){WaitForSingleObject(g_hEvent, INFINITE);if (iTickets > 0){Sleep(1);iTickets--;printf("B remain %d\n", iTickets);}else{break;}SetEvent(g_hEvent);}return 0;
}int main()
{// 线程A 和 线程BHANDLE hThreadA, hThreadB;hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, 0);hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, 0);CloseHandle(hThreadA); CloseHandle(hThreadB);//手动重置 FALSE:设置无信号状态,未触发状态g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);SetEvent(g_hEvent);Sleep(4000);CloseHandle(g_hEvent);system("pause");return 0;
}
4.运行结果
代码运行的总结果如下。