线程同步之互斥量
互斥锁:互斥锁是一个可以处于两态之一的变量:解锁和加锁。
Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex主要的作用是用于互斥。Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。值为0, 表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待;值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0。
Mutex可以被抽象为四个操作:
- 创建 CreateMutex(NULL, false, NULL);
- 加锁 WaitForSingleObject(g_hMutex, INFINITE);
- 解锁 ReleaseMutex(g_hMutex);
- 销毁 CloseHandle(g_hMutex);
WaitForSingleObject()函数解析:
WaitForSingleObject是一种Windows API函数,当等待仍在挂起状态时,句柄被关闭,那么函数行为是未定义的。该句柄必须具有 SYNCHRONIZE 访问权限。
WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,
线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
注意点:
1.互斥锁与临界区类似,都是有创建、进入、离开、销毁等过程。但是互斥量属于内核对象,运行较慢,而临界区属于用户对象,运行速度较快。
2.创建互斥锁的第二参数,注意:
TRUE代表主线程拥有互斥对象 但是主线程没有释放该对象 ,互斥对象谁拥有, 谁释放 。
FLASE代表当前没有线程拥有这个互斥对象。
3.
函数原型:
HANDLE WINAPI CreateMutex(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in_opt LPCTSTR lpName );
lpMutexAttributes : 第一个参数表示安全控制,一般直接传入NULL。
bInitialOwner第二个参数用来确定互斥量的初始拥有者。
如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态,表示互斥量为创建线程拥有。
如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。
lpName第三个参数用来设置互斥量的名称,在多个进程中的线程就是通过名称来确保它们访问的是同一个互斥量。
固有特点(优点+缺点):
1、是一个系统核心对象,所以有安全描述指针,用完了要 CloseHandle 关闭句柄,这些是内核对象的共同特征;
2、因为是核心对象,所以执行速度会比 Critical Sections 慢几乎100倍的时间(当然只是相比较而言);
Mutex和Critical Section都是主要用于限制多线程(Multithread)对全局或共享的变量、对象或内存空间的访问。下面是其主要的异同点(不同的地方用绿色表示)。
|
Mutex |
Critical Section |
性能和速度 |
慢。 Mutex 是内核对象,相关函数的执行 (WaitForSingleObject, ReleaseMutex)需要用户模式(User Mode)到内核模式 (Kernel Mode)的转换,在x86处理器上这种转化一般要 发费600个左右的 CPU指令周期。 |
快。 Critical Section本身不是内核对象,相关函数 (EnterCriticalSection,LeaveCriticalSection) 的调用一般都在用户模式内执行,在x86处理器上 一般只需要发费9个左右的 CPU指令周期。只有 当想要获得的锁正好被别的线程拥有时才会退化 成和Mutex一样,即转换到内核模式,发费600个 左右的 CPU指令周期。 |
能否跨越进程(Process)边界 |
可以 |
不可 |
定义写法 |
HANDLE hmtx; |
CRITICAL_SECTION cs; |
初始化写法 |
hmtx= CreateMutex (NULL, FALSE, NULL); |
InitializeCriticalSection(&cs); |
结束清除写法 |
CloseHandle(hmtx); |
DeleteCriticalSection(&cs); |
无限期等待的写法 |
WaitForSingleObject (hmtx, INFINITE); |
EnterCriticalSection(&cs); |
0等待(状态检测)的写法 |
WaitForSingleObject (hmtx, 0); |
TryEnterCriticalSection(&cs); |
任意时间等待的写法 |
WaitForSingleObject (hmtx, dwMilliseconds); |
不支持 |
锁释放的写法 |
ReleaseMutex(hmtx); |
LeaveCriticalSection(&cs); |
能否被一道用于等待其他内核对象 |
可以(使用WaitForMultipleObjects, WaitForMultipleObjectsEx, MsgWaitForMultipleObjects, MsgWaitForMultipleObjectsEx等等) |
不可 |
当拥有锁的线程死亡时 |
Mutex变成abandoned状态,其他的等待线程可以获得锁。 |
Critical Section的状态不可知(undefined), 以后的动作就不能保证了。 |
自己会不会锁住自己 |
不会(对已获得的Mutex,重复调用WaitForSingleObject不会 锁住自己。但最后你别忘了要调用同样次数的 ReleaseMutex) |
不会(对已获得的Critical Section,重复调用 EnterCriticalSection不会锁住自己。但最后 你别忘了要调用同样次数的 LeaveCriticalSection) |
源代码: // MutexDemo.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <Windows.h> #include <process.h> #define THREADNUM 5 HANDLE g_hMutex=NULL; int g_nCount = 0; unsigned _stdcall ThreadProc(void *lParam) { for (int i = 0; i < 1000000; ++i) { //获得互斥锁的拥有权 WaitForSingleObject(g_hMutex, INFINITE); g_nCount++; //释放互斥锁的拥有权,避免死锁 ReleaseMutex(g_hMutex); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { //创建互斥锁 //TRUE代表主线程拥有互斥对象 但是主线程没有释放该对象 互斥对象谁拥有 谁释放 // FLASE代表当前没有线程拥有这个互斥对象 g_hMutex= CreateMutex(NULL, false, NULL); if (g_hMutex == NULL) { printf("CreatrMutex Error :%d", GetLastError()); } HANDLE pThreads[THREADNUM]; //创建线程 for (int i = 0; i < THREADNUM; ++i) { pThreads[i]=(HANDLE)_beginthreadex(NULL, 0, ThreadProc, NULL, 0, NULL); if (pThreads[i] == 0) { continue; i--; } } WaitForMultipleObjects(THREADNUM, pThreads, TRUE, INFINITE); printf("g_nCount=%d", g_nCount); //释放 for (int i = 0; i < THREADNUM; ++i) { CloseHandle(pThreads[i]); } getchar(); return 0; }
3、因为是核心对象,而且可以命名,所以可以跨进程使用;
4、Mutex 使用正确的情况下不会发生死锁;
5、在“等待”一个 Mutex 的时候,可以指定“结束等待”的时间长度;
6、可以检测到当前拥有互斥器所有权的线程是否已经退出!Wait……函数会返回:WAIT_ABANDONED