四种进程或线程同步互斥的控制方法:
1、互锁函数:能够很保证原子形式访问资源
2、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
3、互斥量:为协调共同对一个共享资源的单独访问而设计的。
4、信号量:为控制一个具有有限数量用户资源而设计。
5、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
互锁函数:
互锁函数的家族十分的庞大,可以查看msdn(http://msdn2.microsoft.com/en-us/library /ms686360.aspx)以InterLocked开始的函数都是互锁函数。
InterlockedExchangeAdd();
InterlockedExchange();
InterlockedCompareExchange();
...还有很多
举例:InterlockedExchangeAdd()
global g_c;//在多线程中共享
g++ //在多线程中大多数情况会导致g_c值错误
InterlockedExchangeAdd(&g_c,1)//替代上面
用互锁函数的优点是:他的速度要比其他的 CriticalSection,Mutex,Event,Semaphore快很多。
临界区:
在无法用互锁函数解决同步问题时可以使用临界区,“以原子操作方式”对共享资源进行访问。
临界区的操作函数:
CRITICAL_SECTION cs;//创建临界区对象
InitializeCriticalSection(&cs);//初始化临界区
EnterCriticalSection() 进入临界区
...
LeaveCriticalSection() 离开临界区
DeleteCriticalSection(&cs);//删除临界区
MFC为临界区进行了封装,提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代 码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。
当然我们自己也可以对临界区进行简单的封装:
class CCriticalEx
{
CRITICAL_SECTION m_hCs;
CCriticalEx(){InitializeCriticalSection(&m_hCs)};
BOOL Lock(){return EnterCriticalSection(&m_hCs)};
BOOL UNLock(){return LeaveCriticalSection(&m_hCs)};
~CCriticalEx(){DeleteCriticalSection(&m_hCs)}
};
临界区优点:使用起来非常容易,它内部使用互锁函数,能够快速的运行;主要缺点:无法使用它们对多个进程中各个线程进行同步
上面介绍的是用户模式实现线程同步的方法,优点是同步速度快,但是不一定能够满足线程运行速度的需求;
事件:(是内核对象)
HANDLE CreateEvent( ); //创建一个事件;
注:如果创建未通知的人工重置事件,主线程调用setevent();给事件发通知信号,此时系统的多个辅助线程都会进入调度状态,此时如果计算机有多个处理器,那么能够真正的实现同时运行;
如果是自动重置的事件,那么主线程调用SetEvent后系统只允许一个辅助线程变为可以调度状态,并且没办法保证那个线程占有;
OpenEvent() 打开一个事件
SetEvent() 设置事件为通知状态
ReSetEvent();设置事件为未通知状态 //通常自动重置事件,系统成功等待到事件时,会自动将事件设置为未通知,基本上自动重置事件不 必调用此函数;
//等待函数
WaitForSingleObject() 等待一个事件
WaitForMultipleObjects() 等待多个事件
例:
HANDLE g_hEvent;//gobal;
g_hEvent = CreateEvent;
//thread1
WaitForSingleObject(g_hEvent)
...
setevent(g_hEvent);
//thread2
..
在线程进行初始化后通知另外一个线程执行剩余操作时,经常用到事件对象。
信号量:
允许多个线程同时共享资源,这与操作系统pv相同,它指出了同时访问共享资源的线程最大数目。同时限制在同一时刻访问资源的最大线程数目;
信号量规则:
资源数目>0时 ,发出信号量信号;
资源数目>0时,不发出信号量信号;
当前资源不能为负;不能大于最大资源数;
操作函数:
CreateSemaphore() 创建信号量时即要同时 指出允许的最大资源计数和当前可用资源计数。
OpenSemaphore() 打开一个信号量
ReleaseSemaphore() 对信号量的当前资源数量进行递增
WaitForSingleObject() 等待信号量
WaitForMultipleObjects()
假如没有客户机线程提出请求时,服务器不允许线程池中的任何线程变成调度线程,但是假如有3个客户机请求到来,那么3个都应该处于可调度状态。 使用信号量就可以很好的对资源监控和对线程的调度。
CreateSemaphore() 创建信号量时即要同时 指出允许的最大资源计数和当前可用资源计数。 一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1, 只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许 其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数 加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针 LONG lInitialCount, // 初始计数 LONG lMaximumCount, // 最大计数 LPCTSTR lpName // 对象名指针 ); |
参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过 4294967295。lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。 OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:
HANDLE OpenSemaphore( DWORD dwDesiredAccess, // 访问标志 BOOL bInheritHandle, // 继承标志 LPCTSTR lpName // 信号量名 ) |
在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计 数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。 ReleaseSemaphore()的函数原型为:
BOOL ReleaseSemaphore( HANDLE hSemaphore, // 信号量句柄 LONG lReleaseCount, // 计数递增数量 LPLONG lpPreviousCount // 先前计数 ); |
该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将 lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和 WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进 入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。
信号量的使用特点使其更适用于对Socket(套接 字)程序中线程的同步。 例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为每一个用户对服务器的页面请求设置一个线 程 ,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的 线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:
// 信号量对象句柄 HANDLE hSemaphore; UINT ThreadProc15(LPVOID pParam) { // 试图进入信号量关口 WaitForSingleObject(hSemaphore, INFINITE); // 线程任务处理 AfxMessageBox("线程一正在执行!"); // 释放信号量计数 ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } UINT ThreadProc16(LPVOID pParam) { // 试图进入信号量关口 WaitForSingleObject(hSemaphore, INFINITE); // 线程任务处理 AfxMessageBox("线程二正在执行!"); // 释放信号量计数 ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } UINT ThreadProc17(LPVOID pParam) { // 试图进入信号量关口 WaitForSingleObject(hSemaphore, INFINITE); // 线程任务处理 AfxMessageBox("线程三正在执行!"); // 释放信号量计数 ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } …… void CSample08View::OnSemaphore() { // 创建信号量对象 hSemaphore = CreateSemaphore(NULL, 2, 2, NULL); // 开启线程 AfxBeginThread(ThreadProc15, NULL); AfxBeginThread(ThreadProc16, NULL); AfxBeginThread(ThreadProc17, NULL); } |
互斥对象:
与临界区有些类似,拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。
操作函数:
CreateMutex()
OpenMutex()
ReleaseMutex()//释放其拥有的互斥对象
WaitForSingleObject()和WaitForMultipleObjects();
例子:
int tickets=100;
void main()
{
hMutex = CreateMutex(NULL, FALSE, NULL);
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
while(TRUE)
{
Sleep(10000);
}
}
DWORD WINAPI Fun1Proc( LPVOID lpParameter )
{
while(TRUE)
{
if(tickets>0)
{
WaitForSingleObject(hMutex, INFINITE);
Sleep(1);
cout<<"thread1 sell ticket : "<<tickets--<<endl;
ReleaseMutex(hMutex);
}
else
break;
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
if(tickets>0)
{
WaitForSingleObject(hMutex, INFINITE);
Sleep(1);
cout<<"thread2 sell ticket : "<<tickets--<<endl;
ReleaseMutex(hMutex);
}
else
break;
}
return 0;
}
MFC都对事件,信号量,互斥对象进行了相应的描述:CEvent,CMutex,CSemaphore。
备注:http://blog.csdn.net/changewang/archive/2006/12/08/1435353.aspx对多线程多种同步机制有很好的讲解