Windows编程之多线程事件对象(Event Object)用法详解

目录

一、前言

二、基础用法

三、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 和 线程B
    HANDLE 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 和 线程B
    HANDLE 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.运行结果

        代码运行的总结果如下。

 

  • 33
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值