互斥对象与事件对象实现线程同步

互斥对象是系统 内核维护的一种 数据结构,它保证了对象对单个线程的访问权
互斥对象的结构:包含了一个使用数量,一个线程ID,一个计数器
使用数量是指有多少个线程在调用该对象
线程ID是指互斥对象维护的线程的ID
计数器表示当前线程调用该对象的次数
为了创建互斥对象,需要调用函数:CreateMutex,该函数可以创建或打开一个命名的或匿名的互斥对象,然后程序就可以利用该互斥对象完成线程间的同步。
CreateMutex函数的原型声明:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, 安全属性结构指针,可为NULL,表示互斥对象使用默认安全性
BOOL bInitialOwner, //指定互斥对象的初始拥有者,如果该值为真,则创建这个互斥对象的线程获得该对象的所有权,否则该线程将不获得所创建的互斥对象的所有权。
LPCTSTR lpName //设置互斥对象的名字,NULL表示匿名的互斥对象
);
如果创建成功,函数返回互斥对象的句柄。如果创建一个命名的互斥对象,并在CreateMutex函数调用前,该命名的互斥对象存在,那么该函数返回已经存在的这个互斥对象的句柄,而这时调用GetLastError函数将返回ERROR_ALREADY_EXISTS.因此可以利用这点保证应用程序只有一个实例运行。
在调用CreateMutex函数创建一个命名的互斥对象后,如果其返回值是一个有效的句柄,那么可以接着调用GetLastError函数,如果该函数返回ERROR_ALREADY_EXISTS,表明先前已经创建了这个命名的互斥对象,因此就可以知道先前已经有该应用程序的一个实例在运行了。当然如果GetLastError函数返回的不是ERROR_ALREADY_EXISTS,说明这个互斥对象是新创建的。
如果一个线程拥有了一个互斥对象后,当该线程运行完成后就要释放该互斥对象,不然其他的线程得不到互斥对象则无法运行
用 BOOL ReleaseMutex(HWND);HWND为需要释放的互斥对象的句柄,函数调用成功返回非0值,否则返回0值。函数实现线程释放该互斥对象的所有权,所有,以后其他线程就可以使用该互斥对象了。
它的具体作用是当有同一个线程多次获得该互斥对象时,每调用它一次将互斥对象的计数器减一,直到减到零为止,此时释放互斥对象,并将互斥对象中的线程id置零。该线程释放该互斥对象。
 
通过调用WaitForSingleObject函数获得互斥对象的所用权。
函数原型:
DWORD WaitForSingleObject(
HANDLE hHandle,//请求的互斥对象的句柄 ,一旦互斥对象处于有信号状态,则该函数就返回。否则,该函数就会一直等待,这样就会暂停线程的执行。
DWORD dwMilliseconds//指定的等待时间间隔。单位毫秒。如果指定的时间间隔已过,即使所请求的对象仍然处于无信号状态,WaitForSingleObject也会返回。如果该参数为0,那么WaitForSingleObject函数将测试该对象的状态并立即返回。如果该参数设置为INFINITE,则处于永远等待。直到等待的对象处于有信号状态才会返回。
 );
如果第二个参数是0,则是测试对象的状态后立即返回
如果是INFINITE,则一直测试对象状态直到接受到信号
它的使用条件是,互斥对象在哪个线程中被创建,就在哪个线程里面释放。因为调用的时候会检查当前线程的id是不是与互斥对象中保存的id一致,若一致,则此次操作有效,不一致,则无效。
 
#include <windows.h>//调用windows API 函数
#include <iostream.h>
int tickets=10;
HANDLE hMutex;	//互斥对象句柄
int index = 0;
//线程1函数
DWORD WINAPI Fun1Proc( LPVOID lpParameter )
{
	while( true )
	{
		//请求事件对象,如果互斥对象hMutex处于信号状态,则进入保护代码
		//如果事件对象处于非信号状态,则等待
		WaitForSingleObject( hMutex, INFINITE );
		if( tickets > 0 )
		{
			Sleep(1);
			cout << "线程1卖出一张票." << tickets-- << endl;
		}
		else
		{
			break;
		}
		releaseMutex(hMutex);
	}
	return 0;
}
//线程2函数
DWORD WINAPI Fun2Proc( LPVOID lpParameter )
{
	while( true )
	{
		WaitForSingleObject( hMutex, INFINITE );
		if( tickets > 0 )
		{
			Sleep(1);
			cout << "线程2卖出一张票." << tickets-- << endl;
		}
		else
		{
			break;
		}
		releaseMutex(hMutex);
	}
	return 0;
}



//main函数是主线程入口函数,然后可以创建其他新线程
void main()
{
	HANDLE hThread1;
	HANDLE hThread2;


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

	hMutex = CreateMutex(NULL,false,NULL);//创建互斥对象
	Sleep(400);	
	cout << "over." << endl;

}




注意:

WaitForSingleObject函数的调用的位置,如果两个线程函数中调用WaitForSingleObject函数的代码放在while循环之前,并把ReleaseMutex函数的调用放在while循环结束之后,这时就会出现只有一个线程售票,
对互斥对象来说,谁拥有谁释放,
 
事件对象和 互斥对象,一样都属于 内核对象,它包含一个使用计数,一个用于标识该事件是一个自动重置还是一个人工重置的布尔值,和另一个用于指定该事件处于已通知状态还是未通知状态的布尔值。
由上面所述,可见事件对象可分为两种,一种是人工重置的,另一种是自动重置的。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。而当一个自动事件得到通知时,等待该事件的所有线程中只有一个线程变为可调度线程。
事件对象有两种不同的类型:人工重置的事件对象和自动重置的事件对象。当人工重置的事件得到通知时,等待该事件的所有线程都可以变为可调度线程,当一个自动重置的事件得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。
可以调用Windows API中的CreateEvent。它可以创建一个有名的或匿名的事件对象,函数原型如下 HANDLE CreateEvent( LPSECURITY_ATTRIBUTES    lpEventAttributes ,//NULL为默认安全属性
BOOL  bManualReset ,//true为人工重置的事件,false为自动重置的事件
BOOL  bInitialState ,//true初始事件对象为有信号状态,false为无信号状态
LPCTSTR  lpName //NULL为匿名的事件对象
);
第一参数 lpEventAttributes是一个指向SECURITY_ATTRIBUTES结构 指针,由它决定该函数返回的句柄是否能够被子进程继承,如果为NULL,则不能被继承。通常指定为NULL(即默认的安全机制)。
第二参数 bManualReset,若指定为TRUE,则创建一个人工重置的事件对象,此时可以利用函数
BOOL ResetEvent(HANDLE hEvent)人工重置其状态为无信号的;若指定为FALSE,系统将创建一个自动重置对象,系统将自动将其状态置为无信号的。
BOOL SetEvent(HANDLE hEvent)是事件对象状态为有信号的。
第三参数   bInitialState,若为TRUE则初始化创建的事件对象为有信号的(signaled),否则将创建的事件对象初始化为无信号的(nonsignaled)。
 
第四参数   lpName, 若指定为NULL,则创建一个匿名的事件对象。
返回值,如果函数成功,将返回一个该函数创建的事件对象的句柄。如果创建的事件对象是有名的,并且在调用该函数前,已经有一个同名的事件对象创建了,那么该函数将返回一个指向已存在的事件对象的句柄,并且调用GetLastError(void)函数时,将返回ERROR_ALREADY_EXISTS;如果 函数调用失败,那么它将返回NULL。
#include <windows.h>//调用windows API 函数
#include <iostream.h>

//创建人工重置事件内核对象,不能实现线程的同步
//只能创建自动设置事件内核对象实现线程的同步
int tickets=10;
HANDLE g_hEvent;	//事件对象句柄

//线程1函数
DWORD WINAPI Fun1Proc( LPVOID lpParameter )
{
	while( true )
	{
		//请求事件对象,如果事件对象g_hEvent处于信号状态,则进入保护代码
		//如果事件对象处于非信号状态,则等待
		WaitForSingleObject( g_hEvent, INFINITE );
	//	ResetEvent( g_hEvent );	//设置事件对象为无信号状态
		if( tickets > 0 )
		{
			Sleep(1);
			cout << "线程1卖出一张票." << tickets-- << endl;
			SetEvent( g_hEvent );
		}
		else
		{
			SetEvent( g_hEvent );
			break;
		}
	}
	return 0;
}
//线程2函数
DWORD WINAPI Fun2Proc( LPVOID lpParameter )
{
	while( true )
	{
		WaitForSingleObject( g_hEvent, INFINITE );
	//	ResetEvent( g_hEvent );//设置事件对象为无信号状态
		if( tickets > 0 )
		{
			Sleep(1);
			cout << "线程2卖出一张票." << tickets-- << endl;
			SetEvent( g_hEvent );//设置事件对象为有信号状态
		}
		else
		{
			SetEvent( g_hEvent );
			break;
		}
	}
	return 0;
}



//main函数是主线程入口函数,然后可以创建其他新线程
void main()
{
	HANDLE hThread1;
	HANDLE hThread2;

	//创建人工重置事件内核对象,事件处于无信号状态
//	g_hEvent = CreateEvent( NULL, true, false, NULL );
	//事件一直处于有信号状态。
	//g_hEvent = CreateEvent( NULL, true, true, NULL );
//	SetEvent(g_hEvent);

	//创建自动重置事件内核对象,事件处于有信号状态
	//只用一个线程成为可调度线程
	g_hEvent = CreateEvent( NULL, false, true, NULL );

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

	Sleep(400);
	//关闭所创建的事件对象句柄
	CloseHandle( g_hEvent );
	cout << "over." << endl;

}

注意:创建人工重置事件内核对象,不能实现线程的同步,只能创建自动设置事件内核对象实现线程的同步
在自动设置事件对象线程同步中,操作系统会将该事件对象设置无信号状态,这样,当对所有保护的代码执行完成之后,需要调用SetEvent函数将该事件对象设置为有信号状态。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值