基于上篇的多线程编程的基本内容,本篇开始Windows操作系统下各种常用的多线程资源同步对象。
(1)windows线程资源同步之临界区:两个重要的 Windows API 函数 WaitForSingleObject 和 WaitForMultipleObjects,前者是一次操作一个资源同步对象,后者同时操作多个资源
同步对象,区别和注意点如下:
1.DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); //参数hhandle表示需要等待的内核对象,参数dwMilliseconds 实际上是一个 unsigned long 类
型,设置 INFINITE 表示无限等待,在 Windows 上可以调用WaitForSingleObject等待的常见对象如下表所示:
可以被等待的对象 等待对象成功的含义 对象类型
线程 等待线程结束 HANDLE
Process 等待进程结束 HANDLE
Event(事件) 等待 Event 有信号 HANDLE
Mutex (互斥体) 等待持有 Mutex 的线程释放该 Mutex,等待成功,拥有该 Mutex HANDLE
Semaphore(信号量) 等待该 Semaphore 对象有信号 HANDLE
函数的返回类型有:WAIT_FAILED -调用失败,可以用GetLastError()得到具体的错误码,WAIT_OBJECT_0-表示成功等待到设置的对象,WAIT_TIMEOUT-等待超时
WAIT_ABANDONED-当等待的对象是 Mutex 类型时,如果持有该 Mutex 对象的线程已经结束,但是没有在结束前释放该 Mutex,此时该 Mutex 已经处于废弃状态,其行为是未知的,
不建议再使用。
2. DWORD WaitForMultipleObjects( DWORD nCount,const HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds); //nCount 指定对象数组的长度,lpHandles表示需要
等待的对象数组指针,bWaitAll表示是否等待所有对象数组有信号,可设置true或者false,设置false只要有一个对象有信号即会返回,在设置bWaitAll 为false的情况下, 除了和上述介绍
的返回值是 WAITFAILED和,,WAITTIMEOUT 以外,由于是多个对象同步,返回值 WAIT_OBJECT_0 to (WAIT_OBJECT_0 + nCount– 1),比如:现在等待三个对象 A1、A2、A3,
它们在数组 lpHandles 中的下标依次是 0、1、2,某次 WaitForMultipleObjects 返回值是 Wait_OBJECT_0 + 1,则表示对象 A2 有信号,导致 WaitForMultipleObjects 调用成功返回。同
理对于WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount– 1)。
windows的临界区对象操作API函数:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); //初始化临界区对象
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); //销毁临界区对象
BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); //尝试进入临界区,如果是,返回true,无法进入则阻塞,返回false
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); //进入临界区
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); //离开临界区
注意:此临界区对象是互斥的,只能一个线程访问持有,为了避免死锁,进入和离开临界区函数需要成对使用,为了避免因函数有多个出口造成的编码疏漏,可使用 RAII 封装临界
区对象类的方法,此API函数为Windows 系统多线程资源同步最常用的对象之一。
(2)windows线程资源同步之Event:Event是windows内核的常用多线程同步的对象,特点是简单易用,但无法精确控制唤醒指定数量的线程。API函数 为:
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset, BOOL bInitialState,LPCTSTR lpName); //参数lpEventAttributes 设置Event对象
的安全属性,设置NULL为默认安全属性;参数bManualReset表示设置 Event 对象受信(变成有信号状态)时的行为,设置true要手动调用 ResetEvent 函数去将 Event 重置成无信号
状态,设置false表示Event 事件对象受信后会自动重置为无信号状态;参数 bInitialState 设置 Event 事件对象初始状态是否是受信的,true表示有信号,false表示无信号;参数 lpName
可以设置 Event 对象的名称,可设置为NULL,Event对象可以通过名称在进程之前不同进程之间共享;返回值为 NULL表示创建失败。
BOOL SetEvent(HANDLE hEvent); //设置Event对象从无信号变成有信号状态
BOOL ResetEvent(HANDLE hEvent); //设置Event对象从有信号变成无信号状态
(3)windows线程同步之Mutex:windows的Mutex(互斥量)在同一时刻只能属于一个线程,也可以不属于任何线程,具有排他性,创建Mutex可设置它属于的线程,其他线程要获取调
用WaitForSingleObject 进行申请,创建 Mutex 的 API 是 CreateMutex,释放 Mutex 的 API 是ReleaseMutex :
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName); //参数 lpMutexAttributes 与Event函数类似,设置
lpMutexAttribute为NULL,参数 bInitialOwner表示调用创建Mutex的线程是否立即拥有该对象,true为拥有,fasle为不拥有;参数 lpName为Mutex 对象的名称,和Event对象一样,多个
线程之间可通过名称共享;返回值为NULL表示创建失败;
BOOL ReleaseMutex(HANDLE hMutex); // 参数 hMutex 即需要释放所有权的 Mutex 对象句柄
(4)windows线程同步之Semaphore:Semaphore 也是 Windows 多线程同步常用的对象之一,与Event、Mutex区别不同的是可以信号量存在资源计数,可以精确控制唤醒的线程数
目。创建 Semaphore 对象的 API 函数和增加信号量资源个数的API如下所示:
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,LONG lInitialCount, LONG lMaximumCount,LPCTSTR lpName); //参数
lpSemaphoreAttributes 指定对象的安全属性,设置为NULL;参数 lInitialCount 指定初始可用资源数量,每调用一次 WaitForSingleObject 获得 Semaphore 对象,该对象的资源计数会
减少一个,参数 lMaximumCount 最大资源数量上限,设置大于0,使用ReleaseSemaphore 增加资源个数不能大于这个上限值;参数 lpName 指定 Semaphore 对象的名称,可通过
名称进行跨进程共享;返回值为NULL表示创建失败。
BOOL ReleaseSemaphore( HANDLE hSemaphore,LONG lReleaseCount, LPLONG lpPreviousCount); //新增信号量的资源个数个数,参数 hSemaphore 是需要操作的信号量
句柄;参数 lReleaseCount,需要增加的资源数量;参数 lpPreviousCount 是一个 long 型(32 位系统上 4 个字节)的指针,返回上一次资源的数量。
信号量根据当前资源的个数分配消费者,当资源数量为0时,所有消费者处于挂起状态,当新资源到来时,消费者会被唤醒。
(5)让程序只启动一个实例的方法:使用线程内核同步对象,首次启动这个进程,这个进程会调用 CreateMutex 函数创建一个名称为“MySingleInstanceApp”的互斥体对象。当再次准备
启动一份这个进程时,再次调用 CreateMutex 函数,由于该名称的互斥体对象已经存在,将会返回已经存在的互斥体对象地址,此时通过 GetLastError() 函数得到的错误码是
ERROR_ALREADY_EXISTS 表示该名称的互斥体对象已经存在,此时我们激活已经存在的前一个实例,然后退出当前进程即可。如下函数实现:
bool CheckInstance()
{
HANDLE hSingleInstanceMutex = CreateMutex(NULL, FALSE, _T("MySingleInstanceApp"));
if (hSingleInstanceMutex != NULL)
{
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
return true;
}
}
return false;
}