mutex和semaphore都是内核对象,是用来实现多进程间或多线程锁机制的基础。本文将要介绍两者的使用方式。
0. 多线程锁机制涉及的Windows API
创建mutex内核对象,用来作为线程间或进程间通信的API接口如下
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:第三个参数用来设置互斥量的名称,多线程就是通过名称来确保它们访问的是同一个互斥量。
(Mutex不仅可以多线程使用,也可以跨进程使用,所以其名称对于整个系统而言是全局的,故而起名字时尽量复杂)。
既然是关于多线程的锁使用问题,那么显然需要介绍下创建线程的API,函数原型:
HANDLE WINAPI CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
函数说明:
第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。
第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址,传入函数名即可。
第四个参数是传给线程函数的参数。
第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。
函数返回值:
成功返回新线程的句柄,失败返回NULL。通过该API的返回值HANDLE,可以看到操作系统将函数、线程、进程、文件、窗口等都统一看作“文件”对象,故而都可以使用Handle统一来指引到目的地址。
既然是使用锁机制,那么显然需要介绍锁的获取方式,即acquire(lock)的应该采用的API,其中最为重要的莫过于WaitForSingleObject和WaitForMultipleObjects两个API。
WaitForSingleObject
函数功能:等待函数 – 使线程进入等待状态,直到指定的内核对象被触发。
函数原形:
DWORD WINAPI WaitForSingleObject(
HANDLEhHandle,
DWORDdwMilliseconds
);
函数说明:
第一个参数为要等待的内核对象,该内核对象只需支持信号通知即可,如event, mutex, process, thread, semaphore。
第二个参数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待。
WaitForMultipleObjects
是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象
函数原型为:
DWORD WaitForMultipleObjects(
DWORD nCount, // number of handles in the handle array
CONST HANDLE *lpHandles, // pointer to the object-handle array
BOOL fWaitAll, // wait flag
DWORD dwMilliseconds // time-out interval in milliseconds
);
参数解析:
DWORD 就是 Double Word, 每个word为2个字节的长度,DWORD双字即为4个字节,每个字节是8位。
nCount 指定列表中的句柄数量,最大值为MAXIMUM_WAIT_OBJECTS(64)
*lpHandles 句柄数组的指针。lpHandles为指定对象句柄组合中的第一个元素 HANDLE类型可以为(Event,Mutex,Process,Thread,Semaphore)数组
bWaitAll 等待的类型,如果为TRUE,表示除非对象都发出信号,否则就一直等待下去;如果FALSE,表示任何对象发出信号即可
dwMilliseconds指定要等候的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去。
关于信号量semaphore的介绍较为复杂,是因为信号量使用的场景更为复杂。首先介绍创建信号量的API接口函数
HANDLE WINAPI CreateSemaphore(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes
_In_ LONG lInitialCount,
_In_ LONG lMaximumCount,
_In_opt_ LPCTSTR lpName
);
第一个参数:安全属性,如果为NULL则是默认安全属性
第二个参数:信号量的初始值,要>=0且<=第三个参数
第三个参数:信号量的最大值,即最大资源数目
第四个参数:信号量的名称,一般不涉及到跨进程使用基本都是输入NULL的。
返回值:指向信号量的句柄,如果创建的信号量和已有的信号量重名,那么返回已经存在的信号量句柄
ReleaseSemaphore
函数功能:为指定的信号量增加指定的资源计数
HANDLE ReleaseSemaphore (
HANDLE hSemaphore,
LONG lReleaseCount,
PLONG plPreviousCount
);
第一个参数:目标信号量句柄
第二个参数:本次释放指定的增加资源计数
第三个参数:返回当前资源计数的原始值,若设置NULL,则不返回。
信号量的使用方法或流程如下:
1、创建一个信号量:CreateSemaphore;
2、使用信号量名字,打开一个已经存在的信号量:OpenSemaphore;
3、监听并获得信号量的一个占有权:WaitForSingleObject、WaitForMultipleObjects 等一类等待的函数……(可能造成阻塞);
4、释放信号量的占有权,增加信号量的资源计数:ReleaseSemaphore;
5、关闭信号量,将其从内核中删除:CloseHandle;
信号量semaphore的使用规则:
1. 如果当前资源计数大于0,即信号量的可用资源数大于0,那么信号量处于触发状态,可响应任何监听获取请求;
2. 如果当前资源计数等于0,那么信号量处于未触发状态;那么系统会让调用线程进入等待状态。
CreateSemaphore(NULL,0,1,NULL); 当第二个参数为0时,表示当前信号量的可用资源数目为0,表示创建进程在创建信号量之时并占用了该信号量,其余监听信号量的线程就会进入等待状态,直到创建信号量的宿主线程释放信号量。
3. 当前资源计数绝对不会大于最大资源计数。
1. Mutex的多线程锁机制使用案例
#include <iostream>
#include <windows.h>
#include <time.h>
using namespace std;
HANDLE g_hMutex = NULL;
const int g_Number = 3; //代表线程对象的数目
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter);
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter);
DWORD WINAPI ThreadProc3(__in LPVOID lpParameter);
int main()
{
clock_t start_time = clock();
//g_hMutex = CreateMutex(NULL,FALSE,NULL);
//TRUE代表主线程拥有互斥对象 但是主线程没有释放该对象 互斥对象谁拥有 谁释放
g_hMutex = CreateMutex(NULL,TRUE,NULL);
printf("主线程创建时便占有了互斥对象,没有释放,所以其他子线程无法使用。\n");
ReleaseMutex(g_hMutex);
printf("主线程释放了互斥对象,其他子线程可以开始使用。\n");
// FLASE代表当前没有线程拥有这个互斥对象
HANDLE hThread[ g_Number ] = {0};
int first = 1, second = 2, third = 3;
hThread[ 0 ] = CreateThread(NULL,0,ThreadProc1,(LPVOID)first,0,NULL);
hThread[ 1 ] = CreateThread(NULL,0,ThreadProc2,(LPVOID)second,0,NULL);
hThread[ 2 ] = CreateThread(NULL,0,ThreadProc3,(LPVOID)third,0,NULL);
WaitForMultipleObjects(g_Number,hThread,TRUE,INFINITE); //INFINITE代表一直等下去,该位单位为ms
CloseHandle( hThread[0] );
CloseHandle( hThread[1] );
CloseHandle( hThread[2] );
CloseHandle( g_hMutex );
clock_t end_time = clock();
cout<<"Running time is:"<<static_cast<double>(end_time - start_time)/CLOCKS_PER_SEC*1000<<"ms"<<endl;
return 0;
}
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter)
{
WaitForSingleObject(g_hMutex, INFINITE);//等待互斥量
cout<<(int)lpParameter<<endl;
ReleaseMutex(g_hMutex);//释放互斥量
return 0;
}
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter)
{
WaitForSingleObject(g_hMutex, INFINITE);//等待互斥量
cout<<(int )lpParameter<<endl;
ReleaseMutex(g_hMutex);//释放互斥量
return 0;
}
DWORD WINAPI ThreadProc3(__in LPVOID lpParameter)
{
WaitForSingleObject( g_hMutex, INFINITE);//等待互斥量
cout<<(int)lpParameter<<endl;
ReleaseMutex(g_hMutex);//释放互斥量
return 0;
}
2. semaphore的多线程锁机制使用案例
#include <iostream>
#include <windows.h>
#include <time.h>
using namespace std;
const int g_Number = 3;
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter);
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter);
DWORD WINAPI ThreadProc3(__in LPVOID lpParameter);
HANDLE hSemp1,hSemp2,hSemp3;
int main()
{
clock_t start_time = clock();
hSemp1 = CreateSemaphore(NULL,0,3,NULL);
printf("信号量初始便被主线程占用,需要主线程释放后,其余线程才能使用\n");
char ch = getchar();
ReleaseSemaphore(hSemp1,1,NULL);
printf("信号量可能使得多个进程并行,交替执行。\n");
//hSemp2 = CreateSemaphore( NULL,1,1,NULL);
//hSemp3 = CreateSemaphore(NULL,1,1,NULL);
HANDLE hThread[ g_Number ] = {0};
int first = 1, second = 2, third = 3;
hThread[ 0 ] = CreateThread(NULL,0,ThreadProc1,(LPVOID)first,0,NULL);
hThread[ 1 ] = CreateThread(NULL,0,ThreadProc2,(LPVOID)second,0,NULL);
hThread[ 2 ] = CreateThread(NULL,0,ThreadProc3,(LPVOID)third,0,NULL);
WaitForMultipleObjects(g_Number,hThread,TRUE,INFINITE);
CloseHandle( hThread[0] );
CloseHandle( hThread[1] );
CloseHandle( hThread[2] );
CloseHandle( hSemp1 );
CloseHandle( hSemp2 );
CloseHandle( hSemp3 );
clock_t end_time = clock();
cout<<"Running time is:"<<static_cast<double>(end_time - start_time)/CLOCKS_PER_SEC*1000 <<"ms"<<endl;
return 0;
}
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter)
{
WaitForSingleObject(hSemp1, INFINITE);//等待信号量
cout<<(int)lpParameter<<endl;
ReleaseSemaphore(hSemp1,1,NULL);//释放信号量
return 0;
}
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter)
{
WaitForSingleObject(hSemp1, INFINITE);//等待信号量
cout<<(int )lpParameter<<endl;
ReleaseSemaphore(hSemp1,1,NULL);//释放信号量
return 0;
}
DWORD WINAPI ThreadProc3(__in LPVOID lpParameter)
{
WaitForSingleObject( hSemp1, INFINITE);//等待信号量
cout<<(int)lpParameter<<endl;
ReleaseSemaphore(hSemp1,1,NULL);//释放信号量
return 0;
}