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)释放这个对象,让别的线程使用。

浙公网安备 33010602011771号