临界区 |
临界区是一种最简单的同步对象,它只可以在同一进程内部使用。它的作用是保证只有一个线程可以申请到该对象 | |
void InitializeCriticalSectio | 产生临界区 |
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)
{
}
//in main function
{
}
互斥量 |
互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它
创建互斥量:
HANDLE CreateMutex(
);
打开一个存在的互斥量:
HANDLE OpenMutex(
);
释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权:
BOOL ReleaseMutex(
);
关闭互斥量:
BOOL CloseHandle(
);
对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。当等待成功后 WaitForSingleObject 函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。WaitForSingleObject 函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权
int iCounter=0;
DWORD threadA(void* pD)
{
}
//in main function
{
}
WaitForSingleObject 这个函数可以作用于:
Mutex
Event
Semaphore
Job
Process
Thread
Waitable timer
Console input
互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以我们可以使用 WaitForSingleObject 来等待进程和线程退出。(至于信号灯,事件的用法我们接下来会讲)我们在前面的例子中使用了 WaitForMultipleObjects 函数,这个函数的作用与 WaitForSingleObject 类似但从名字上我们可以看出,WaitForMultipleObjects 将用于等待多个对象变为有信号状态,函数原型如下:
DWORD WaitForMultipleObjects(
);
dwMilliseconds
返回值意义:
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(
);
打开信号灯:
HANDLE OpenSemaphore(
);
释放信号灯:
BOOL ReleaseSemaphore(
);
关闭信号灯:
BOOL CloseHandle(
);
DWORD threadA(void* pD)
{
}
//in main function
{
}
信号灯有时用来作为计数器使用,一般来讲将其初始值设置为 0,先调用 ReleaseSemaphore 来增加其计数,然后使用 WaitForSingleObject 来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置 WaitForSingleObject 的等待时间为 0 来检查信号灯当前是否为 0。
事件 |
事件对象用于通知其他进程/线程某件操作已经完成方面的作用是很大的,而且如果有的任务要在进程尖进行协调采用等待其他进程中线程结束的方式是不可能实现的
事件对象可以一两种方式创建,一种为自动重置,在其他线程使用 WaitForSingleObject 等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用 WaitForSingleObject 等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。事件相关的 API 如下:
创建事件对象:
HANDLE CreateEvent(
);
打开事件对象:
HANDLE OpenEvent(
);
设置事件为无信号状态:
BOOL ResetEvent(
);
设置事件有无信号状态:
BOOL SetEvent(
);
关闭事件对象:
BOOL CloseHandle(
);
在MFC中对于各种同步对象都提供了相对应的类
在这些类中封装了上面介绍的对象创建,打开,控制,删除功能。但是如果要使用等待功能则需要使用另外两个类:CSingleLock 和 CMultiLock。这两个类中封装了 WaitForSingleObject 和 WaitForMultipleObjects 函数。如果大家觉的需要可以看看这些类的定义,我想通过上面的介绍可以很容易理解,但是在对象同步问题上我觉得使用 API 函数比使用 MFC 类更为直观和方便。