临界区 |
临界区是一种最简单的同步对象,它只可以在同一进程内部使用。它的作用是保证只有一个线程可以申请到该对象 | |
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); | 产生临界区 |
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); | 删除临界区 |
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); | 进入临界区,相当于申请加锁,如果该临界区正被其他线程使用则该函数会等待到其他线程释放 |
bool TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); | 进入临界区,相当于申请加锁,和EnterCriticalSection不同如果该临界区正被其他线程使用则该函数会立即返回FALSE,而不会等待 |
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); | 退出临界区,相当于申请解锁 |
下面的示范代码演示了如何使用临界区来进行数据同步处理:
//全局变量
int iCounter=0;
CRITICAL_SECTION criCounter;
DWORD threadA(void* pD)
{
int iID=(int)pD;
for(int i=0;i<8;i++)
{
EnterCriticalSection(&criCounter);
int iCopy=iCounter;
Sleep(100);
iCounter=iCopy+1;
printf("thread %d : %d\n",iID,iCounter);
LeaveCriticalSection(&criCounter);
}
return 0;
}
//in main function
{
//创建临界区
InitializeCriticalSection(&criCounter);
//创建线程
HANDLE hThread[3];
CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待线程结束
//至于 WaitForMultipleObjects 的用法后面会讲到。
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
//删除临界区
DeleteCriticalSection(&criCounter);
printf("\nover\n");
}
互斥量 |
互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它
创建互斥量:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全信息
BOOL bInitialOwner, // 最初状态,
//如果设置为真,则表示创建它的线程直接拥有了该互斥量,而不需要再申请
LPCTSTR lpName // 名字,可以为NULL,但这样一来就不能被其他线程/进程打开
);
打开一个存在的互斥量:
HANDLE OpenMutex(
DWORD dwDesiredAccess, // 存取方式
BOOL bInheritHandle, // 是否可以被继承
LPCTSTR lpName // 名字
);
释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权:
BOOL ReleaseMutex( //作用如同LeaveCriticalSection
HANDLE hMutex // 句柄
);
关闭互斥量:
BOOL CloseHandle(
HANDLE hObject // 句柄
);
对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。当等待成功后 WaitForSingleObject 函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。WaitForSingleObject 函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权
int iCounter=0;
DWORD threadA(void* pD)
{
int iID=(int)pD;
//在内部重新打开
HANDLE hCounterIn=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sam sp 44");
for(int i=0;i<8;i++)
{
printf("%d wait for object\n",iID);
WaitForSingleObject(hCounterIn,INFINITE);
int iCopy=iCounter;
Sleep(100);
iCounter=iCopy+1;
printf("\t\tthread %d : %d\n",iID,iCounter);
ReleaseMutex(hCounterIn);
}
CloseHandle(hCounterIn);
return 0;
}
//in main function
{
//创建互斥量
HANDLE hCounter=NULL;
if( (hCounter=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
{
//如果没有其他进程创建这个互斥量,则重新创建
hCounter = CreateMutex(NULL,FALSE,"sam sp 44");
}
//创建线程
HANDLE hThread[3];
CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待线程结束
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
//关闭句柄
CloseHandle(hCounter);
}
}
WaitForSingleObject 这个函数可以作用于:
Mutex
Event
Semaphore
Job
Process
Thread
Waitable timer
Console input
互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以我们可以使用 WaitForSingleObject 来等待进程和线程退出。(至于信号灯,事件的用法我们接下来会讲)我们在前面的例子中使用了 WaitForMultipleObjects 函数,这个函数的作用与 WaitForSingleObject 类似但从名字上我们可以看出,WaitForMultipleObjects 将用于等待多个对象变为有信号状态,函数原型如下:
DWORD WaitForMultipleObjects(
DWORD nCount, // 等待的对象数量
CONST HANDLE *lpHandles, // 对象句柄数组指针
BOOL fWaitAll, // 等待方式,
//为TRUE表示等待全部对象都变为有信号状态才返回,为FALSE表示任何一个对象变为有信号状态则返回
DWORD dwMilliseconds // 超时设置,以ms为单位,如果为INFINITE表示无限期的等待
);
dwMilliseconds 的范围从 0 - 0x7fffffff 或者 INFINITE - 0xffffffff
返回值意义:
WAIT_OBJECT_0 到 (WAIT_OBJECT_0 + nCount – 1):当 fWaitAll 为 TRUE 时表示所有对象变为有信号状态,当 fWaitAll 为 FALSE 时使用返回值减去 WAIT_OBJECT_0 得到变为有信号状态的对象在数组中的下标。
WAIT_ABANDONED_0 到 (WAIT_ABANDONED_0 + nCount – 1):当 fWaitAll 为 TRUE 时表示所有对象变为有信号状态,当 fWaitAll 为 FALSE 时表示对象中有一个对象为互斥量,该互斥量因为被关闭而成为有信号状态,使用返回值减去 WAIT_OBJECT_0 得到变为有信号状态的对象在数组中的下标。
WAIT_TIMEOUT:表示超过规定时间。
信号灯 |
信号灯有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于 0 时为有信号状态,小于等于 0 时为无信号状态,所以可以利用 WaitForSingleObject 进行等待,当 WaitForSingleObject 等待成功后信号灯的值会被减少 1,直到释放时信号灯会被增加 1。用于信号灯操作的 API 函数有下面这些:
创建信号灯:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性,NULL表示使用默认的安全描述
LONG lInitialCount, // 初始值
LONG lMaximumCount, // 最大值
LPCTSTR lpName // 名字
);
打开信号灯:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, // 存取方式
BOOL bInheritHandle, // 是否能被继承
LPCTSTR lpName // 名字
);
释放信号灯:
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 句柄
LONG lReleaseCount, // 释放数,让信号灯值增加数
LPLONG lpPreviousCount // 用来得到释放前信号灯的值,可以为NULL
);
关闭信号灯:
BOOL CloseHandle(
HANDLE hObject // 句柄
);
DWORD threadA(void* pD)
{
int iID=(int)pD;
//在内部重新打开
HANDLE hCounterIn=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44");
for(int i=0;i<3;i++)
{
printf("%d wait for object\n",iID);
WaitForSingleObject(hCounterIn,INFINITE);
printf("\t\tthread %d : do database access call\n",iID);
Sleep(100);
printf("\t\tthread %d : do database access call end\n",iID);
ReleaseSemaphore(hCounterIn,1,NULL);
}
CloseHandle(hCounterIn);
return 0;
}
//in main function
{
//创建信号灯
HANDLE hCounter=NULL;
if( (hCounter=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
{
//如果没有其他进程创建这个信号灯,则重新创建
hCounter = CreateSemaphore(NULL,2,2,"sam sp 44");
}
//创建线程
HANDLE hThread[3];
CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待线程结束
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
//关闭句柄
CloseHandle(hCounter);
}
信号灯有时用来作为计数器使用,一般来讲将其初始值设置为 0,先调用 ReleaseSemaphore 来增加其计数,然后使用 WaitForSingleObject 来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置 WaitForSingleObject 的等待时间为 0 来检查信号灯当前是否为 0。
事件 |
事件对象用于通知其他进程/线程某件操作已经完成方面的作用是很大的,而且如果有的任务要在进程尖进行协调采用等待其他进程中线程结束的方式是不可能实现的
事件对象可以一两种方式创建,一种为自动重置,在其他线程使用 WaitForSingleObject 等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用 WaitForSingleObject 等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。事件相关的 API 如下:
创建事件对象:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性,NULL表示使用默认的安全描述
BOOL bManualReset, // 是否为人工重置
BOOL bInitialState, // 初始状态是否为有信号状态
LPCTSTR lpName // 名字
);
打开事件对象:
HANDLE OpenEvent(
DWORD dwDesiredAccess, // 存取方式
BOOL bInheritHandle, // 是否能够被继承
LPCTSTR lpName // 名字
);
设置事件为无信号状态:
BOOL ResetEvent(
HANDLE hEvent // 句柄
);
设置事件有无信号状态:
BOOL SetEvent(
HANDLE hEvent // 句柄
);
关闭事件对象:
BOOL CloseHandle(
HANDLE hObject // 句柄
);
在MFC中对于各种同步对象都提供了相对应的类
在这些类中封装了上面介绍的对象创建,打开,控制,删除功能。但是如果要使用等待功能则需要使用另外两个类:CSingleLock 和 CMultiLock。这两个类中封装了 WaitForSingleObject 和 WaitForMultipleObjects 函数。如果大家觉的需要可以看看这些类的定义,我想通过上面的介绍可以很容易理解,但是在对象同步问题上我觉得使用 API 函数比使用 MFC 类更为直观和方便。