多线程 事件对象 关键代码段

本文深入探讨了线程同步的各种机制,包括事件对象、互斥对象、关键代码段和信号量的使用及其优缺点。通过具体代码示例,解析了如何避免线程间的冲突和死锁,确保数据的一致性和程序的稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

using namespace   std;
DWORD WINAPI Fun1Proc(
	LPVOID lpParameter);
DWORD WINAPI Fun2Proc(
	LPVOID lpParamter);

HANDLE g_hEvent;
int tickets = 100;
int main()
{
	HANDLE hThread1;
	HANDLE hThread2;

	g_hEvent = CreateEvent(NULL, TRUE, false, NULL);
	SetEvent(g_hEvent);
	hThread1 = CreateThread(false, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(false, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	Sleep(4000);
	CloseHandle(g_hEvent);
	return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	while (TRUE)
	{
		WaitForSingleObject(g_hEvent, INFINITE);
		ResetEvent(g_hEvent);
		if (tickets>0)
		{
			
			Sleep(1);
			cout << "thread1 sell ticket:" << tickets-- << endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;
		}
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
	while (TRUE)
	{
		WaitForSingleObject(g_hEvent, INFINITE);
		ResetEvent(g_hEvent);
		if (tickets>0)
		{
			Sleep(1);
			cout << "thread2 sell ticket:" << tickets-- << endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;

		}
		

	}
	return 0;
}

 

 

g_hEvent = CreateEvent(NULL, TRUE, false, NULL);

创建的一个人工重置事件对象,当线程等待到该对象的说有权之后,需要调用ResetEven函数手动将该对象设置为无信号状态。

这样才能实现两个线程之间的同步。

但是运行之后发现出现thread1(2) sell ticket 0;说明程序出现问题。说明程序仍然没有实现线程间的同步。上述做法存在两个原因:

1.在单CPU情况下,只能运行一个线程,若1线程运行,得到事件对象,但正好这个时候时间片停止了,没来的及执行ResetEvet,将事件对象转为无信号状态,这时执行线程2,线程2就得到该事件对象,也就是说两个线程同时进入保护程序。,结果不可预料。

2.多CPU情况下,线程1,2可以同时进行,这时在调用SetEvent函数将其设置为有信号状态已经不起作用了,因为两个线程同时进入所保护程序,结果不可预料。

为了实现线程间的同步,也不应该使用人工重置的事件对象,而应该使用自动重置的事件对象

g_hEvent = CreateEvent(NULL, false, false, NULL);

创建的一个自动重置事件对象

当自动重置的事件得到通知时,等待该事件的线程中只有一个线程变得可调度线程,,若线程1得到事件对象,因为他是一个自动事件,所以操作系统会将该事件对象设置为无信号状态。

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

using namespace   std;
DWORD WINAPI Fun1Proc(
	LPVOID lpParameter);
DWORD WINAPI Fun2Proc(
	LPVOID lpParamter);

HANDLE g_hEvent;
int tickets = 100;
int main()
{
	HANDLE hThread1;
	HANDLE hThread2;

	g_hEvent = CreateEvent(NULL, false, false, NULL);
	SetEvent(g_hEvent);
	hThread1 = CreateThread(false, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(false, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	Sleep(4000);
	CloseHandle(g_hEvent);
	return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	while (TRUE)
	{
		WaitForSingleObject(g_hEvent, INFINITE);
		//ResetEvent(g_hEvent);
		if (tickets>0)
		{
			
			Sleep(1);
			cout << "thread1 sell ticket:" << tickets-- << endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;
		}
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
	while (TRUE)
	{
		WaitForSingleObject(g_hEvent, INFINITE);
		//ResetEvent(g_hEvent);
		if (tickets>0)
		{
			Sleep(1);
			cout << "thread2 sell ticket:" << tickets-- << endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;

		}
		

	}
	return 0;
}

 

当人工重置的事件对象得到通知时,该事件对象的所有线程均变为可调度线程:当一个自动重置的对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程,同时操作系统会将该事件对象设置无信号状态,这样,当对所有报读的代码执行完成之后,需要调用SetEvent函数将该事件对象设置为有信号状态。而人工重置的事件对象,在一个线程得到该事件对象之后,操作系统并不会将该事件对象设置为无信号状态,除非显示的调用ResetEvent函数将其设置为无信号状态,否则该对象会一直是有信号状态。

 

关键代码段

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

using namespace std;
CRITICAL_SECTION g_sr;
DWORD WINAPI Fun1Proc(
	LPVOID lpParamter
	);
DWORD WINAPI Fun2Proc(
	LPVOID lpParamter
	);
	
int main()
{
	HANDLE pThread1;
	HANDLE pThread2;
	InitializeCriticalSection(&g_sr);
	pThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	pThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(pThread1);
	CloseHandle(pThread2);
	Sleep(4000);
	DeleteCriticalSection(&g_sr);
	return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParamter)
{
	while (TRUE)
	{
		
		EnterCriticalSection(&g_sr);
		Sleep(1);
		cout << "Fun1Proc" << endl;
		LeaveCriticalSection(&g_sr);
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParamter)
{
	while (TRUE)
	{
		
		EnterCriticalSection(&g_sr);
		Sleep(1);
		cout << "Fun2Proc" << endl;
		LeaveCriticalSection(&g_sr);
	}
	return 0;
}

互斥对象,事件对象,关键代码段的比较

互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时,无法设定超时值。

通常,在编写多线程程序并需要实现线程同步时,首选关键代码段,由于它的使用比较简单,如果是在MFC程序中使用的话,可以在类的构造函数中调用InitializeCriticalSection函数在该类的析构函数中调用DeleteCriticalSection函数在所需保护的代码前面调用EnterCriticalSection函数,在访问完所需保护的资源后,调用LeaveCriticalSection函数。可见,关键代码段在使用上是非常方便的,但有几点需要注意:一是在程序中调用了EnterCriticalSection后,一定要相应的调用LeaveCriticalSection函数,否则其他等待该临界区对象所有权的线程将无法执行。二是如果访问关键代码段时,使用了多个临界区对象,就要注意防止线程死锁的发生。另外,如果需要在多个线程间的各个线程间实现同步的话,可以使用互斥对象和事件对象。

前提:假设有个经理,下面有5个项目组.经理同时最多能接5个项目,每个项目组一次只能做一个项目。

信标Semaphore:信标相当于,5个项目组都争先恐后的争夺项目。如果经理有5个项目,那么5个项目组都可以做。如果经理有3个项目,那就有2个项目没事情做,在等待。如果经理没有项目,那么5个组,都在闲着。

事件Event:相当于,外面有个项目,经理把项目接回来了。站在门口大喊:有项目了。喊完了,经理就不管了这个项目又没有人处理。

互斥Mutex:相当于,经理手上有1个项目。下面的组,主动地争夺项目,谁抢到谁就有事情做,没抢到的就得待着。

形象的理解:

关键段与互斥量都有“线程所有权”概念,可以将“线程所有权”理解成旅馆的房卡,在旅馆前台登记名字拥有房卡后是可以多次进出房间的,其它人则无法进入直到你交出房卡。每个线程必须先通过EnterCriticalSection或WaitForSingleObject来尝试获得“线程所有权”才能调用LeaveCriticalSection或ReleaseMutex。否则会调用失败,这就相当于伪造房卡去办理退房手续——由于登记本上没有你的名字所以会被拒绝。 互斥量能很好的处理“遗弃”情况,因此在多进程之间可以放心的使用。

 

事件与信号量相当于管停车位的,信号量的大小相当于停车位容量多大,比如一共有5个停车位,来了3辆车,那可以直接进去,但是现在又来了3辆车,只能按先后顺序进去,有一辆车得等,直到停车场上有车开走了,这辆车才能进去。而事件相当于只有一个停车位的停车场。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值