多线程基础之二:mutex和semaphore使用方法

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; 
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值