多作务操作系统之间各任务在使用同一资源时会发生冲突,包括读写的一致性问题,唯一性外设等
这时需要通过同步来使资源在同一时刻被合理的占用。
WINDOWS下可以通过临界区(CRITICAL_SECTION)、互斥量(MUTEX)、信号量(SEMAPHORE),事件(EVENT)
比较
临界区 | 互斥量 | 信号量 | 事件 | |
跨进程 | 否 | 是 | 是 | 是 |
速度 | 快 | 慢 | 慢 | 慢 |
1 临界区: 是一种最简单的同步对象,它只可以在同一进程内部使用。它的作用是保证只有一个线程可以申请到该对象
操作函数:
VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
int iCounter =0;
CRITICAL_SECTION criCounter;
DWORD threadA(void* pD)
{
int iID=(int)pD;
HANDLE hCounterIn=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sample");
for (int i=0;i<8;i++)
{
EnterCriticalSection(&criCounter);
int iCopy=iCounter;
Sleep(100);
iCounter=iCopy+1;
printf("\t\tthread %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(3,hThread,TRUE,INFINITE);
DeleteCriticalSection(&criCounter);
printf("nover\n");
}
2 互斥量:互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部使用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它
操作函数:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName);
HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
BOOL ReleaseMutex(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,"sample");
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,"sample"))==NULL)
{
hCounter=CreateMutex(NULL,FALSE,"sample");
}
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);
}
3 信号灯:信号灯有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于0时为有信号状态,小于等于0时为无信号状态,所以可以利用WaitForSingleObject进行等待,当WaitForSingleObject等待成功后信号灯的值会被减少1,直到释放时信号 灯会被增加1.用于信号灯操作的API函数有下面这些:
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount, LONG lMaximumCount,LPCTSTR lpName);
HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL hInheritHandle,LPCTSTR lpName);
BOOL ReleaseSemaphore(HANDLE hSemaphore,LONG lReleaseCount;LPLONG lpPreviousCount);
BOOL CloseHandle(HANDLE hObject);
信号灯有时用来作为计数器使用,一般来讲将其初值设置为0,先调用ReleaseSemaphore来增加其计数,然后使用WaitForSingelObject来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置WaitForSingleObject的等待时间为0来检查信号灯当前是否为0.
DWORD threadA(void* pD)
{
int iID=(int)pD;
HANDLE hCounterIn=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sample");
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 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,"sample"))==NULL)
{
hCounter=CreateSemaphore(NULL,2,2,"sample");
}
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);
}
4 事件:事件对象用于通知其他进程/线程某件操作已经完成方面的作用是很大的,而且如果有的任务要在进程间进行协调采用等待其他进程中线程结束的方式是不可能实现的
事件对象可以以两种方式创建,一种为自动重置,在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在共创线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的纯种结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。事件相关的API如下:
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventattributes,
BOOL bManualReset, BOOL bInitalstate, 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函数。
DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles, BOOL fWaitAll, DWORD dwMillseconds);
返回值意义:
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: 表示超过规定时间