Windows中多线程的基础知识2——事件对象

 上一节我们介绍了线程同步、以及利用互斥对象实现线程同步的方法。这一节,我们继续介绍另一种线程同步的方法:事件对象。如果对线程概念、互斥对象概念不清楚的同学,请查看Windows中多线程的基础知识——1互斥对象

1 事件对象

1.1 事件对象概念

 事件对象也属于内核对象,它包含以下三个成员:
 1. 使用计数;
 2. 用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值;
 3. 用于指明该事件处于已通知状态还是未通知状态的布尔值。
 事件对象有两种不同的类型:人工重置的事件对象和自动重置的事件对象。当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程。当一个自动重置的事件对象得到通知时,等待该事件对象的线程只有一个线程变为可调度对象。

1.2 事件对象相关函数

一、创建事件对象

 创建事件对象的函数原型为:

HANDLE WINAPI CreateEvent(
__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
__in BOOL bManualReset,
__in BOOL bInitialState,
__in_opt LPCTSTR lpName
);

函数参数说明:
 lpEventAttributes: 一般为NULL;
 bManualReset:创建的Event是人工重置事件对象,还是自动重置事件对象。如果TRUE,表示该函数将创建一个人工重置对象;如果此参数为FALSE,为自动重置事件对象。如果是人工重置事件对象,当线程等待到该对象的所有权之后,需要调用ResetEvent函数手动的将事件对象设置为无信号状态;如果是自动重置对象,当线程等待该对象的所有权之后,系统会自动将对象设置为无信号状态。
 bInitialState:初始状态,true,有信号,false无信号
 lpName:事件对象的名称。
 该函数创建一个Event同步对象,如果CreateEvent调用成功的话,会返回新生成的对象的句柄,否则返回NULL。
 请注意,当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程;当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程可变为可调度对象,同时操作系统会将该事件对象设置为无信号状态。
 MSDN的原文说明为:

When the state of a manual-reset event object is signaled, it remains signaled until it is explicitly reset to nonsignaled by the ResetEvent function. Any number of waiting threads, or threads that subsequently begin wait operations for the specified event object, can be released while the object’s state is signaled.
When the state of an auto-reset event object is signaled, it remains signaled until a single waiting thread is released; the system then automatically resets the state to nonsignaled. If no threads are waiting, the event object’s state remains signaled.

二、设置事件对象状态

 SetEvent函数将把指定的事件对象设置为有信号状态,函数原型为:

BOOL WINAPI SetEvent( __in HANDLE hEvent);

 其中的参数为将要设置其状态的事件对象的句柄。

三、重置事件对象状态

 ResetEvent函数将把指定的事件对象设置为无信号状态,函数原型为:

BOOL WINAPI ResetEvent( __in HANDLE hEvent);

2 利用事件对象实现线程同步的例程

#include <windows.h>
#include <iostream>

HANDLE g_hEvent;
int iTickets = 1000;

//线程1的入口函数
DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter)
{
	while (true)
	{
		//请求事件对象
		WaitForSingleObject(g_hEvent, INFINITE);
		if (iTickets > 0)
		{
			std::cout << "thread1 sell tickes:" << iTickets-- << std::endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;
		}
	}

	return 0;
}

//线程2的入口函数
DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter)
{
	while (true)
	{
		//请求事件对象
		WaitForSingleObject(g_hEvent, INFINITE);
		if (iTickets > 0)
		{
			std::cout << "thread2 sell tickes:" << iTickets-- << std::endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;
		}
	}

	return 0;
}

int main()
{
	HANDLE hThread1;
	HANDLE hThread2;

	//创建人工重置事件内核对象
	g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	//将事件设置为有信号状态
	SetEvent(g_hEvent);

	//创建线程
	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	//以下代码为了防止主线程提前退出,从而使进程退出,也就终止了进程下所有线程的运行
	while (true)
	{
		if (iTickets > 0)
			Sleep(200);
		else
		{
			//关闭事件对象句柄
			CloseHandle(g_hEvent);
			getchar();
			return 0;
		}
	}
}

 和上一节一样,以上代码也是在main中创建了两个线程(并且即刻执行),两个线程都会访问全局变量iTickets ,每卖一张票,就会将此全局变量递减1。为了实现线程同步,我们在main中创建了事件对象,并且设置事件对象为自动重置事件对象,且设置其处于有信号状态。在两个线程执行时,会请求这个事件对象,首先得到事件对象的线程得以继续执行,与此同时设置事件对象为无信号状态(因此,其他线程就得不到该事件对象,也就只能暂停等待),当执行完毕时,恢复事件对象为有信号状态。接下来,在系统调度下,两个线程会相继执行,无论哪个线程得到事件对象,都会暂停另一个线程的执行,这样,就实现了线程同步。
在这里插入图片描述
下一节,我们学习利用临界区实现线程同步的方法:
Windows中多线程的基础知识3——关键代码段(临界区)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Santiago

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值