【Window】线程同步方式3——事件

12 篇文章 4 订阅

第一节:【Window】创建线程的3种方式
第二节:【Window】线程同步概述
第三节:【Window】线程同步方式1——临界区(关键代码段)
第四节:【Window】线程同步方式2——互斥量
第五节:【Window】线程同步方式3——事件
第六节:【Window】线程同步方式4——信号量

在这里插入图片描述

1 事件

事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。

事件(Event)是WIN32提供的最灵活的线程间同步方式,事件可以处于激发状态(signaled or true)或未激发状态(unsignal or false)。根据状态变迁方式的不同,事件可分为两类:

  • 手动设置:这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEventResetEvent来进行设置。
  • 自动恢复:一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。

使用”事件”机制应注意以下事项:

  • 如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要与系统命名空间中的其它全局命名对象冲突;
  • 事件是否要自动恢复;
  • 事件的初始状态设置。

2 信号量包含的几个操作原语

1)CreateEvent

  1. 函数功能:创建事件

  2. 函数原型:

       HANDLE  CreateEvent(
       LPSECURITY_ATTRIBUTES   lpEventAttributes,
       BOOL   bManualReset,
       BOOL   bInitialState,
       LPCTSTR  lpName
       );
    
  3. 函数说明:

    lpEventAttributes表示安全控制,一般直接传入NUL
    bManualReset确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
    InitialState表示事件的初始状态,传入TRUR表示已触发。
    pName表示事件的名称,传入NULL表示匿名事件。

2)OpenEvent

  1. 函数功能:根据名称获得一个事件句柄,打开事件。

  2. 函数原型:

    HANDLE  OpenEvent(
    		DWORD  dwDesiredAccess,
    		BOOL  bInheritHandle,
    		LPCTSTR  lpName //名称
    );
    
  3. 函数说明:

    dwDesiredAccess表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。
    InheritHandle表示事件句柄继承性,一般传入TRUE即可。
    pName表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。

3)SetEvent

  1. 函数功能:设置事件的状态为有标记,释放任意等待线程。如果事件是手工的,此事件将保持有标记直到调用ResetEvent,这种情况下将释放多个线程;如果事件是自动的,此事件将保持有标记,直到一个线程被释放,系统将设置事件的状态为无标记;如果没有线程在等待,则此事件将保持有标记,直到一个线程被释放。
  2. 函数原型:
    BOOL SetEvent(HANDLE  hEvent);
    
  3. 函数说明:
    每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
  4. 返回值:如果操作成功,则返回非零值,否则为0。

4)ResetEvent

  1. 函数功能:将事件设为末触发
  2. 函数原型:
    BOOL  ResetEvent(HANDLE  hEvent);
    

5)WaitForSingleObject

  1. 函数功能:WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。

  2. 函数原型:

    等待一个事件 :
    DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
    等待多个事件 :
    DWORD WaitForMultipleObjects(
          DWORD nCount, // 等待句柄数 
          CONST HANDLE *lpHandles, //指向句柄数组 
          BOOL bWaitAll, //是否完全等待标志 
          DWORD dwMilliseconds //等待时间 
        )
    
  3. 函数说明:

    hHandle对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等
    wMilliseconds定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。
  4. 返回值
    执行成功,返回值指示出引发函数返回的事件。它可能为以下值:

    WAIT_ABANDONED当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。
    WAIT_OBJECT_0指定的对象出有有信号状态
    IT_TIMEOUT等待超时
    IT_FAILED出现错误,可通过GetLastError得到错误代

6)事件的清理与销毁

由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

3 事件说明

  • 首先我们需要创建CreateEvent一个事件对象,它的使用方式是触发方式;
  • 要想被WaitForSingleObject等待到该事件对象必须是有信号的;
  • 事件要想有信号可以用SetEvent手动置为有信号;
  • 要想事件对象无信号可以使用ResetEvent(或者在创建事件对象时就声明该事件对象,WaitForSingleObject自动置为无信号,见上面CreateEvent第二个参数);

打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。
自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。当然事件对象的句柄是要关闭的CloseHandle。

4. 举例

事件对象模拟火车售票的过程:
(利用通知的方式对共同资源的互斥控制)

// EventThread.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include "windows.h"  
using namespace std;

DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int ticket = 27;
HANDLE g_hEvent; //定义事件句柄
void main()
{
    HANDLE hThread1;//线程句柄1
    HANDLE hThread2;//线程句柄2
    g_hEvent = CreateEvent(
        NULL, 
        FALSE, //传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
        TRUE,  // 表示事件的初始状态,传入TRUR表示已触发。
        NULL   //表示事件的名称,传入NULL表示匿名事件。
    );//创建事件对象  
    //线程1先被激活
    hThread1 = CreateThread(
        NULL, //为NULL则表示返回的句柄不能被子进程继承
        0,   //设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。
        FunProc1, //指向线程函数的指针
        NULL, //向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
        0, //控制线程创建的标志,0:表示创建后立即激活
        NULL //保存新线程的id,是指向线程id的指针,如果为空,线程id不被返回
    ); //函数成功,返回线程句柄,否则返回NULL
    hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);
    if (hThread1 != NULL)
        CloseHandle(hThread1);
    if (hThread2 != NULL)
        CloseHandle(hThread2);
    Sleep(1000);             // 让主线程睡眠1秒   
    if (g_hEvent != NULL)
        CloseHandle(g_hEvent);
}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
    while (TRUE)
    {
        WaitForSingleObject(g_hEvent, INFINITE); //阻塞状态:一直等待对象为有信号状态,申请事件g_hEvent; INFINITE:对象被触发信号后,函数才会返回
        //ResetEvent(g_hEvent); //重置为无信号(申请钥匙,得到钥匙)  
        if (ticket > 0) //如果当前票数大于0,线程1使票数减一,
        {
            Sleep(1);
            cout << "ticket 1:" << ticket-- << endl;
            SetEvent(g_hEvent);//设置为有信号(放弃钥匙,不再拥有)  
        }
        else
        {
            SetEvent(g_hEvent);//设置为有信号(放弃钥匙,不再拥有)  
            break;
        }
    }
    return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
    while (TRUE)
    {
        WaitForSingleObject(g_hEvent, INFINITE);
        if (ticket > 0)
        {
            Sleep(1);
            cout << "ticket 2:" << ticket-- << endl;
            SetEvent(g_hEvent);       //设置为有信号  
        }
        else
        {
            SetEvent(g_hEvent);       //设置为有信号  
            break;
        }
    }
    return 0;
}

同步过程分析:

  • g_hEvent=CreateEvent(NULL,FALSE,TRUE,NULL)的意义: 自动重置事件,初始时该事件对象就有信号。

  • 执行顺序:
    ① 开始的时候事件对象具有信号,当第一个线程hThread1申请获得事件对象后,进入if语句,线程hThread1会暂停1毫秒;
    ② 于是第二线程hThread2运行,因为此时g_hEvent已经无信号,故无法申请,所以无法执行下面的程序;
    ③ 此时第一个线程hThread1睡醒,开始执行自己的任务,然后设置对象为有信号(可以被其他线程申请);
    ④ 于是第二个线程hThread2申请得到事件对象,进入if语句,线程hThread2会暂停1毫秒;
    ⑤ 第一线程hThread1运行,因为此时g_hEvent已经无信号,故无法申请,所以无法执行下面的程序。
    ⑥ 此时第二个线程hThread2睡醒,开始执行自己的任务,然后设置对象为有信号(可以被其他线程申请);

    …与此往复,直到退出循环。

在这里插入图片描述


#include "windows.h"    
#include "iostream"    
  
using namespace std;  
DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int ticket = 27;
HANDLE g_hEvent1, g_hEvent2;
void main()
{
    HANDLE hThread1;
    HANDLE hThread2;
    g_hEvent1 = CreateEvent(NULL, FALSE, TRUE, NULL);      //创建事件对象1,自动重置事件,已触发  
    g_hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);     //创建事件对象2,自动重置事件,没触发      
    hThread1 = CreateThread(NULL, 0, FunProc1, NULL, 0, NULL);//创建后立即激活
    hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);//创建后立即激活
    if (hThread1 != NULL)CloseHandle(hThread1);
    if (hThread2 != NULL)CloseHandle(hThread2);
    Sleep(1000);             // 让主线程睡眠1秒     
    if (g_hEvent1 != NULL)CloseHandle(g_hEvent1);
    if (g_hEvent2 != NULL)CloseHandle(g_hEvent2);
    
}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
    while (TRUE)
    {
        WaitForSingleObject(g_hEvent1, INFINITE);//申请有信号的事件对象g_hEvent1    
        //ResetEvent(g_hEvent);       //重置为无信号(申请钥匙,得带钥匙)    
        if (ticket > 0)
        {
            Sleep(1);
            cout << "ticket 1:" << ticket-- << endl;
            SetEvent(g_hEvent2);//设置为有信号,初始状态为无信号;让线程2执行  
        }
        else
        {
            SetEvent(g_hEvent2);//设置为有信号,让线程2执行  
            break;
        }
    }
    return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
    while (TRUE)
    {
        WaitForSingleObject(g_hEvent2, INFINITE);  //申请有信号的事件对象g_hEvent2   
        if (ticket > 0)
        {
            Sleep(1);
            cout << "ticket 2:" << ticket-- << endl;
            SetEvent(g_hEvent1);  //让线程1执行  
        }
        else
        {
            SetEvent(g_hEvent1);
            break;
        }
    }
    return 0;
}
  • 执行顺序:
    ① 开始的时候事件对象g_hEvent1具有信号、g_hEvent2无信号,当第一个线程hThread1申请获得事件对象后,进入if语句,线程hThread1会暂停1毫秒;
    ② 于是第二线程hThread2运行,因为此时g_hEvent2无信号,故无法申请,无法执行;
    ③ 此时第一个线程hThread1睡醒,开始执行自己的任务,然后设置对象g_hEvent2为有信号(可以被其他线程申请);
    ④ 于是第二个线程hThread2申请得到事件对象g_hEvent2,进入if语句,线程hThread2会暂停1毫秒;
    ⑤ 第一线程hThread1运行,此时g_hEvent1有信号,进入if语句。
    …与此往复,直到退出循环。

在这里插入图片描述


#include <Windows.h>
#include <iostream>
using namespace std;

typedef struct  _STRUCT_DATA_
{
	int id; //用于标识出票id
	int tickets;
}_DATA, * _pDATA;

HANDLE g_hEvent;
DWORD WINAPI Fun1(LPVOID lpParam);
DWORD WINAPI Fun2(LPVOID lpParam);

void main()
{
	HANDLE hThread1;
	HANDLE hThread2;

	_DATA stru_data;
	stru_data.id = 0;
	stru_data.tickets = 20;

	g_hEvent = CreateEvent(NULL, FALSE, FALSE, L"Ticket");
	if (g_hEvent) {
		if (ERROR_ALREADY_EXISTS == GetLastError()) {
			cout << "the instance is exist!" << endl;
			return;
		}
	}

	hThread1 = CreateThread(NULL, 0, Fun1, &stru_data, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2, &stru_data, 0, NULL);

	if (hThread1) 
		CloseHandle(hThread1);
	if (hThread2) 
		CloseHandle(hThread2);

	SetEvent(g_hEvent);

	Sleep(4000);
	if (g_hEvent) CloseHandle(g_hEvent);
}

DWORD WINAPI Fun1(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while (TRUE)
	{
		WaitForSingleObject(g_hEvent, INFINITE);
		if (data->tickets > 0)
		{
			Sleep(1);
			cout << "fun1: " << data->id++ ;
			cout << "   thread 1: sell ticket: " << data->tickets-- << endl;
			SetEvent(g_hEvent);
		}
		else {
			SetEvent(g_hEvent);
			break;
		}
	}
	return 0;
}

DWORD WINAPI Fun2(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while (TRUE)
	{
		WaitForSingleObject(g_hEvent, INFINITE);
		if (data->tickets > 0)
		{
			Sleep(1);
			cout << "fun2: " << data->id++;
			cout << "   thread 2: sell  ticket: " << data->tickets-- << endl;
			SetEvent(g_hEvent);
		}
		else {
			SetEvent(g_hEvent);
			break;
		}
	}
	return 0;
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值