互斥对象在线程同步时的使用
1 多线程在资源共享的时候出现的问题
在程序中如果不同线程对同一个对象进行操作的话就有可能出现因为线程切换而导致的问题。例如下面的程序
#include <stdio.h> #include <WinSock2.h> #include <iostream> using namespace std; #pragma comment(lib,"ws2_32.lib") DWORD WINAPIfun1Proc(LPVOID lpParameter); DWORD WINAPIfun2Proc(LPVOID lpParameter); int index = 0; int tickets = 100; 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(hThread2); Sleep(4000); } DWORD WINAPIfun1Proc(LPVOID lpParameter) { while(TRUE) { if(tickets>0) { cout<<"Thread1 sell ticket:"<<tickets--<<endl; } else break; } return0; } DWORD WINAPIfun2Proc(LPVOID lpParameter) { while(TRUE) { if(tickets>0) { cout<<"Thread2 sell ticket:"<<tickets--<<endl; } else break; } return0; }
我们期望的目标是线程1和线程2都一起递减,直到为减到1为止。但是很有可能出现这样的情况:线程1中在if进行判断的时候tickets=1,然后就发生了线程切换,此时tickets的值还没有递减,所以在线程2中判断为真,然后tickets--,tickets = 0,此时线程再发生切换到线程1的话,那么就会出现tickets--,使tickets变成了0,这就不是我们期望的效果了。
2 问题的解决方案:WaitForSingleObject
很容易想到如果把if-else作为一个整体执行不被打断那就好了。这种想法是正确的,于是就有了WaitForSingleObject函数的出现。
DWORD WINAPI WaitForSingleObject( _In_ HANDLE hHandle, _In_ DWORD dwMilliseconds );
<1>这个函数是这样的,只要句柄所指向的内核对象时无信号的,那么这个函数就会一直阻塞下去除非时间经过了dwMilliseconds。所以它就像一个哨兵,必须给出通行证才能过去,没有的话那就一直等着吧。
<2>句柄可以的类型
- Change notification
- Console input
- Event
- Memory resource notification
- Mutex
- Process
- Semaphore
- Thread
- Waitable timer
3 互斥对象Mutex在线程同步上的运用
<1>对象的创建
Mutex他能确保线程对单个资源访问的互斥权。这个内核对象包含一个使用计数,一个线程ID,一个计数器。
HANDLE WINAPI CreateMutex( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, _In_ BOOL bInitialOwner, _In_opt_ LPCTSTR lpName );
lpMutexAttributes:指定安全性,当为NULL,表示采用默认安全性
bInitialOwner:为TRUE表示创建这个对象的线程具有该互斥对象的所有权,反之则不具有
lpName:互斥对象的名称,如果为NULL则表示一个匿名的互斥对象。如果指定名字,并且在CreateMutex调用之前,该命名的互斥对象已经存在,那么函数将返回这个已经存在的互斥对象的句柄,调用GetLastError的话,会得到ERROR_ALREADY_EXISTS。
<2> 对象所有权的释放
ReleaseMutex(HANLDE hMutex);
释放的原则是谁拥有谁释放;拥有几次,则释放几次;线程结束操作系统会自动释放
在释放互斥对象的时候,函数会拿当前线程的ID跟拥有互斥对象的ID比较,如果是同一个ID,那么就释放一次。如果ID不相等,那么就不释放。
对象在拥有一次的时候,还可以再次拥有,例如如下代码:
hMutex = CreateMutex(NULL,TRUE,NULL); WaitForSingleObject(hMutex,INFINITE); ReleaseMutex(hMutex); ReleaseMutex(hMutex);
当主线程拥有Mutex的时候,该对象处于未通知状态,但是当WaitForSingleObject函数请求互斥对象的拥有权的时候,因为ID相等,所以仍然能够请求到这个互斥对象的所有权,这个时候互斥对象内部的计数器就会加1.
操作系统一旦发现线程终止但是没有释放互斥对象的时候,它就会自动的将互斥对象的线程ID设置为0,并且将其计数器归0.
4 最终实现的代码
#include <stdio.h> #include <WinSock2.h> #include <iostream> using namespace std; #pragma comment(lib,"ws2_32.lib") DWORD WINAPIfun1Proc(LPVOID lpParameter); DWORD WINAPIfun2Proc(LPVOID lpParameter); int index = 0; int tickets = 100; HANDLE hMutex; void main() { HANDLE hThread1; HANDLE hThread2; //hMutex =CreateMutex(NULL,FALSE,NULL); hMutex = CreateMutex(NULL,TRUE,NULL); WaitForSingleObject(hMutex,INFINITE); ReleaseMutex(hMutex); ReleaseMutex(hMutex); hThread1 =CreateThread(NULL,0,fun1Proc,NULL,0,NULL); hThread2 =CreateThread(NULL,0,fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); } DWORD WINAPIfun1Proc(LPVOID lpParameter) { while(TRUE) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0) { cout<<"Thread1 sell ticket:"<<tickets--<<endl; } else break; ReleaseMutex(hMutex); } return0; } DWORD WINAPIfun2Proc(LPVOID lpParameter) { while(TRUE) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0) { cout<<"Thread2 sell ticket:"<<tickets--<<endl; } else break; ReleaseMutex(hMutex); } return0; }