31win32编程基础——线程安全之互斥体

上一篇解决线程安全问题(同时读写)的方法是使用临界区,临界区的思路其实就是互斥。

互斥就是:同一时间只有1个线程执行。

同步就是:多个线程交替执行。

阻塞的本质:其实就是无限循环。

互斥体和临界区有什么区别:

1、临界区,没有等待时间,一直等令牌。

2、互斥体可以设置等待时间,如果超时,就会返回,或者线程意外结束,可以通过设置的时间避免死锁。

3、临界区控制的是同1个进程中的多线程对资源的访问。

4、互斥体能控制多进程间的线程对资源的访问。

5、Mutex互斥体没有临界区的效率高。

6、临界区只能用于单进程的资源控制,互斥体可以用于多进程间的资源控制。

使用互斥体之前,先将2个函数:

DWORD WaitForSingleObject(   

HANDLE hHandle,   //内核对象句柄

DWORD dwMilliseconds //等待时间

); 

  

WaitForSingleObject是对SingleObject(单个内核对象)操作的,即:等待单个内核对象的状态发生改变或者超时。

内核对象判断技巧:内核对象都带有LPSECURITY_ATTRIBUTES lpsa, 这个参数(安全属性)

主要对以下内核对象

  • Event            事件
  • Mutex                互斥体
  • Semaphore       信号量
  • Process             进程
  • Thread               线程

dwMilliseconds 这个参数可以设置等待的时间,以毫秒为单位。

或者设置为INFINITE(-1),-1表示这个函数永远阻塞,直到这个对象变为已通知状态。

例子:

WaitForSingleObject(Handle1,5000)当代码执行到这一句的时候,代码会阻塞起来,不会往下执行了。

这个函数就会一直while循环读取Handle1内核对象的状态,当内核对象的状态发生改变了(或者超时了),就跳出循环,继续往下执行。

这里也可以把这个时间当做循环次数,当超过循环次数就跳出while循环。

因此2种情况发生的时候,会继续往下执行。

1、当Handle1内核对象的的状态变为已通知状态。

2、当等待时间超过5s的时候。

这2种状态就会继续执行这个函数后边的代码。不同的内核状态刚开始的状态是不一样的。

每个内核对象都有2种状态,已通知和未通知,其实在内核这个状态就是一个bool变量,TRUE和FALSE。
线程运行的时候为FALSE是未通知状态,线程结束的时候为TRUE,是已通知状态。
因此,当线程:
  当线程运行的时候是未通知状态
  当线程结束的时候是已通知状态
3、根据WaitForSingleObject的返回值判断,到底是内核状态发生改变了,还是因为超时。
当返回值为WAIT_OBJECT_0(0x0)的时候,说明是因为内核状态发生改变才停止阻塞的。
当返回值为WAIT_TIMEOUT(258L)的时候,说明是因为超时才结束的。

 

#include <Windows.h>
#include "OutPut.h"

DWORD WINAPI  ThreadProc( __in  LPVOID lpParameter){


		OutputDebugStringF("111111111\n");
		Sleep(5000);

	return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR  lpCmdLine,int  nCmdShow)
{
	HANDLE hh1=CreateThread(NULL,NULL,ThreadProc,0,NULL,NULL);

	WaitForSingleObject(hh1,2000);
	//WaitForSingleObject(hh1,-1);

	MessageBox(0,0,0,0);//分别设置WaitForSingleObject等待时间,观察Msg弹出来的时机

	CloseHandle(hh1);

	return 0;
}

  实验观察结果,,加深理解。

#include <windows.h>
#include "OutPut.h"

DWORD WINAPI  ThreadProc( __in  LPVOID lpParameter){


		OutputDebugStringF("111111111\n");
		Sleep(5000);

	return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR  lpCmdLine,int  nCmdShow)
{
	HANDLE hh1=CreateThread(NULL,NULL,ThreadProc,0,NULL,NULL);
	/*
	DWORD  hh= WaitForSingleObject(hh1,2000);
	OutputDebugStringF("%d\n",hh);
	hh = WaitForSingleObject(hh1,2000);
	OutputDebugStringF("%d\n",hh);
	hh = WaitForSingleObject(hh1,2000);  //当执行到这的时候,到5s的时候会直接返回。
	OutputDebugStringF("%d\n",hh);
	hh = WaitForSingleObject(hh1,2000);  //从这里开始的Wait都是不阻塞,直接返回,因为到这个地方前边线程已经结束,因此后边相当于没有Wait这句代码,直接输出
	OutputDebugStringF("%d\n",hh);
	hh = WaitForSingleObject(hh1,2000);
	OutputDebugStringF("%d\n",hh);
	hh = WaitForSingleObject(hh1,2000);
	OutputDebugStringF("%d\n",hh);
	hh = WaitForSingleObject(hh1,2000);
	OutputDebugStringF("%d\n",hh);
	*/

	
	DWORD  hh= WaitForSingleObject(hh1,8000);
	//MessageBox(0,0,0,0);
	OutputDebugStringF("%d\n",hh);
	 hh=WaitForSingleObject(hh1,8000);
	OutputDebugStringF("%d\n",hh);
	 hh=WaitForSingleObject(hh1,8000);
	OutputDebugStringF("%d\n",hh);
	  hh=WaitForSingleObject(hh1,8000);
	OutputDebugStringF("%d\n",hh);
	

  观看上边的代码直接结果,学会WaitForSingleObject的用法。

 特别注意:
有的内核对象,象Mutex互斥体,遇到WaitForSingleObject之后,会改变内核对象的状态,然后再使用ReleaseMutex重新激发Mutex对象的状态。
但是如果是线程或者进程的句柄,遇到
WaitForSingleObject,并不会改变线程或者进程内核对象的状态,因此使用的时候,要特别注意,不然容易产生代码逻辑的错误,导致永远阻塞。

DWORD WaitForMultipleObjects(  
DWORD nCount,         //等待内核对象的数量
CONST HANDLE* lpHandles,  //多个内核对象的数组名
BOOL fWaitAll,        //TRUE表示当所有的状态发生改变才返回,FALSE任意1个对象的状态发生改变立即返回
DWORD dwMilliseconds    //等待的时间
 );
理解了WaitForSingleObject,这个WaitForMultipleObjects等待多个内核对象函数就很好理解了。
WaitForMultipleObjects返回值
当fWaitAll为TRUE的时候,返回值WAIT_OBJECT_0(0)代表的是所有对象都已经变为已通知状态。

当fWaitAll为FALSE的时候,返回值代表的是最先变为已通知状态的对象在数组中的索引

当fWaitAll不管为TRUE或FALSE的时候,超时的返回值都是为WAIT_TIMEOUT,表示为超时。

WaitForMultipleObjects(Count,Array,TRUE,5000);     返回值为0或者WAIT_TIMEOUT(0x102),0表示多个内核对象都已变为已通知状态。
WaitForMultipleObjects(Count,Array,TRUE,-1);      返回值为0,当多个内个对象变为已通知状态才返回。
WaitForMultipleObjects(Count,Array,FALSE,5000);      返回值为大于等于0,表示最先变为已通知状态的内个对象在数组中的索引。返回值为WAIT_TIMEOUT(0x102)表示超时才返回。
WaitForMultipleObjects(Count,Array,FALSE,-1);       返回值为大于等于0,表示最先变为已通知状态的内个对象在数组中的索引

 

 使用互斥体示例:

启动2个VS,

 2个进程中的主线程:1个线程创建Mutex对象,另1个进程中的线程获取这个Mutex对象(是软件多开的一种方法)。

 我们临界区的实现,是使用

CRITICAL_SECTION  ics;

InitializeCriticalSection(&ics)初始化
EnterCriticalSection(&ics)         阻塞

  xxxx代码xxxx   执行代码逻辑
LeaveCriticalSection(&ics)     离开临界区
DeleteCriticalSection(&ics)

  实现的。

互斥体的实现就是利用WaitForSingleObject和WaitForMutipleObjects来实现的。

CreateMutex();//创建互斥体

OpenMutex()  //使用互斥体
WaitForSingleObject或者WaitForMultipleObjects

     xxxxx代码xxxxxx

ReleaseMutex

为什么可以利用CreaMutex和WaitForSingleObject实现互斥呢?

当Hmutex = CreaMutex(NULL,FALSE,"XYZ")的时候,CreateMutex()函数的第二个参数是FALSE,表示刚刚创建的这个Mutex不属于一任何线程,也就是没有任何线程拥有这个Mutex,一个Mutex在没有任何线程拥有它的时候是出于已通知状态的,所以以上这段代码中的Mutex内核对象是处于已通知状态的,也就是有信号状态。

当线程启动,执行到WaitForSingleObject(Hmutex ,-1)先判断的状态。前边讲了,WaitForSingleObject如果遇到内核对象是未通知状态,那么就会一直阻塞。

2种情况发生才会继续往下执行:内核对象为已通知状态或者超时。因为-1已经把超时这种状态排除了,所以只有已通知状态才能继续往下执行。

那么不管哪个线程先执行WaitForSingleObject(Hmutex ,-1)先判断的状态,第一个执行这句话的代码的线程,就检查,发现是已通知状态,并修改Mutex为未通知状态,然后继续往下执行。

执行完访问全局变量的代码之后。

使用ReleaseMutex(Hmutex)是将设置为已通知状态。

可以这样理解:

  当CreaMutex(NULL,FALSE,"XYZ")设置Mutex为没有线程拥有这个信号

  当第一个线程遇到WaitForSingleObject就占有这个信号,往下执行

  执行完共享资源之后

  ReleaseMutex(Hmutex)释放这个对象,让别的线程使用。

posted @ 2023-11-02 18:32  一日学一日功  阅读(154)  评论(0)    收藏  举报