线程锁的限制
CRITICAL_SECTION临界区(线程锁):用户态对象,仅本进程内部线程同步,不能跨进程,遇到跨进程共享资源(共享内存、文件、内核页),临界区就无能为力,需要互斥体 Mutex(内核对象)
互斥体核心概念
- 本质:存放在内核中的令牌,属于内核对象,可以跨进程同步
- 工作逻辑:
● 线程拿到互斥体(获取令牌),进入临界区;互斥体变为无信号
● 其它线程调用WaitForSingleObject获取,直接阻塞
● 持有线程调用ReleaseMutex()释放,互斥体变回有信号,等待线程被唤醒 - 内核对象,用完必须CloseHandle()关闭句柄
CreateMutex 函数原型:
HANDLECreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,//安全属性,NULL句柄不可继承BOOL bInitialOwner,//创建者是否直接占有互斥体LPCSTR lpName//互斥体名字,NULL为匿名;有名字就可以跨进程打开);参数详解
- lpMutexAttributes:一般填NULL
- bInitialOwner
○ TRUE:创建出来互斥体是无信号状态,当前线程直接占有互斥体
○ FALSE:创建出来互斥体是有信号状态,任意线程都可以立刻获取 - lpName:互斥体名称,命名互斥体可被别的进程通过名字打开;传NULL代表匿名,只能本进程内使用
✨互斥体递归特性:同一个线程已经拥有互斥体,可以再次WaitForSingleObject获取,不会死锁;获取多少次,就要对应ReleaseMutex多少次
释放互斥体:
BOOLReleaseMutex(HANDLE hMutex);只有当前拥有互斥体所有权的线程,才可以调用 ReleaseMutex 释放
互斥体 vs CRITICAL_SECTION(临界区)高频对比
| 对比项 | CRITICAL_SECTION 临界区 | Mutex 互斥体 (CreateMutex) |
|---|---|---|
| 对象类型 | 用户态对象,非内核对象 | 内核对象 |
| 使用范围 | 仅同一个进程内部线程 | 进程内、跨进程都可以 |
| 等待超时 | 不能设置超时,拿不到锁永久阻塞 | 借助 WaitForSingleObject 可以设置超时时间 |
| 效率 | 速度快,尽量不进入内核 | 速度慢,一定会切换内核态 |
| 线程异常退出 | 线程崩溃不会自动释放锁,其它线程永久卡死 | 持有线程异常终止,系统自动回收互斥体,不会永久死等 |
| 关闭销毁 | DeleteCriticalSection() | CloseHandle() |
互斥体经典用法:程序防多开
原理:创建命名互斥体
● 如果系统里同名互斥体已经存在:CreateMutex依旧返回有效句柄,GetLastError()返回ERROR_ALREADY_EXISTS(183),说明程序已经跑了一份,直接退出
● 如果不存在,则新建互斥体,正常运行程序
HANDLE hMutex=CreateMutex(NULL,FALSE,L"MyApp_Mutex");DWORD dwErr=GetLastError();if(hMutex!=NULL){if(dwErr==ERROR_ALREADY_EXISTS){//已有实例,退出CloseHandle(hMutex);return0;}}else{//创建失败return0;}//主程序逻辑...//程序退出CloseHandle(hMutex);注意:防多开只是互斥体的附加玩法,互斥体本职工作是线程 / 进程同步
其它防多开手段:文件锁、共享内存、窗口查找
场景选型
- 只做进程内部多线程同步:优先 CRITICAL_SECTION 临界区,性能更高
- 跨进程同步:必须使用 Mutex 互斥体
总结
- 临界区用户态,不能跨进程;互斥体是内核对象,支持跨进程
- bInitialOwner=FALSE:互斥体初始有信号;TRUE创建线程直接占有互斥体,初始无信号
- 互斥体可以递归获取;持有线程崩溃,系统自动释放互斥体
- 互斥体可以搭配WaitForSingleObject设置等待超时;临界区 EnterCriticalSection 没有超时
- 命名互斥体可以实现软件防多开,判断GetLastError()==ERROR_ALREADY_EXISTS
- 互斥体内核对象,结束必须CloseHandle;临界区调用DeleteCriticalSection销毁