同步可以保证在同一时刻只有一个线程对某个共享资源有控制权。共享资源包括全局变量、公共数据成员或句柄等。
一.临界区对象实现同步。
(1) 为什么要线程同步,可以参见下面的小例子;
#include "stdafx.h"
#include "process.h"
ULONG gCount1 = 0,gCount2 = 0;
BOOL bRet = 1;
UINT WINAPI ThreadProc1(LPVOID lpParam)
{
while(bRet)
{
gCount1++;
gCount2++;
}
return 0;
}
UINT WINAPI ThreadProc2(LPVOID lpParam)
{
while(bRet)
{
gCount1++;
gCount2++;
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE h[2];
UINT dwThreadID[2];
h[0] = (HANDLE)::_beginthreadex(NULL,NULL,ThreadProc1,NULL,NULL,&dwThreadID[0]);
h[1] = (HANDLE)::_beginthreadex(NULL,NULL,ThreadProc2,NULL,NULL,&dwThreadID[1]);
Sleep(500);
bRet = 0;
::WaitForMultipleObjects(2,h,TRUE,INFINITE);
::CloseHandle(h[0]);
::CloseHandle(h[1]);
cout << "gCount1 ="<< gCount1 <<endl;
cout << "gCount2 ="<<gCount2 << endl;
system("pause");
return 0;
}
运行结果
1. 为什么会出现这种结果:线程1在取出数据gCount2后,时间片正好用完,然后线程2对gCount1和gCount2操作结束后,线程1的时间片回来,还是对自己内存中gCount2的数据操作,完事赋值给原来的gCount;导致线程2对gCount2的操作无效;
2. 在实际开发中,一般不直接调用windows的CreateThread函数,而是使用C++运行期函数_BeginThreadEx();
uintptr_t _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( *start_address )( void * ),
void*arglist,
unsigned initflag,
unsigned *thrdaddr
);
(2) 使用临界区对象:
临界区对象是定义在数据段中的一个CRITICAL_SECTION结构;
1. 任何线程要使用临界区对象要先进行初始化:
void WINAPI InitializeCriticalSection( __outLPCRITICAL_SECTION lpCri int nPriority );
2. 线程调用临界区中对象时要调用:
void WINAPI EnterCriticalSection(__in_out LPCRITICAL_SECTIONlpCriticalSection);
3. 线程要离开临界区的时候,要交还临界区,调用
void WINAPI LeaveCriticalSection(__in_outLPCRITICAL_SECTION lpCriticalSection);
4. 程序不再使用临界区时,要删除临界区
void WINAPI DeleteCriticalSection(__in_outLPCRITICAL_SECTION lpCriticalSection);
(3) 使用临界区对象对上面的程序略作改动:
#include "stdafx.h"
#include "process.h"
ULONG gCount1 = 0,gCount2 = 0;
BOOL bRet = 1;
CRITICAL_SECTION cs;
UINT WINAPI ThreadProc1(LPVOID lpParam)
{
while(bRet)
{
::EnterCriticalSection(&cs);
gCount1++;
gCount2++;
::LeaveCriticalSection(&cs);
}
return 0;
}
UINT WINAPI ThreadProc2(LPVOID lpParam)
{
while(bRet)
{
::EnterCriticalSection(&cs);
gCount1++;
gCount2++;
::LeaveCriticalSection(&cs);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE h[2];
UINT dwThreadID[2];
::InitializeCriticalSection(&cs);
h[0] = (HANDLE)::_beginthreadex(NULL,NULL,ThreadProc1,NULL,NULL,&dwThreadID[0]);
h[1] = (HANDLE)::_beginthreadex(NULL,NULL,ThreadProc2,NULL,NULL,&dwThreadID[1]);
Sleep(500);
bRet = 0;
::WaitForMultipleObjects(2,h,TRUE,INFINITE);
::CloseHandle(h[0]);
::CloseHandle(h[1]);
::DeleteCriticalSection(&cs);
cout << "gCount1 ="<< gCount1 <<endl;
cout << "gCount2 ="<<gCount2 << endl;
system("pause");
return 0;
}
二. 使用互锁函数实现同步
互锁函数为同步访问多线程共享变量提供了一个简单的机制。如果线程在共享内存,可以使用此机制。
常用的函数有:InterlockedIncrement;InterlockedDecrement;InterlockedExchangeAdd;InterlockedExchangePointer,只使用前两个对之前的程序进程一个改动,
UINT WINAPI ThreadProc1(LPVOID lpParam)
{
while(bRet)
{
::InterlockedIncrement(&gCount1);
::InterlockedIncrement(&gCount2);
}
return 0;
}
三. 使用事件内核对象
事件对象有三个成员:nUsageCount(使用记数,为0时撤销此内核对象),bManualReset(是否人工重置),bSignaled(是否受信);
事件对象主要用于进程间通信,因为它是一个内核对象,所以可以跨简称使用。因而可以达到线程同步的目的;
1. 基本函数:
1) 要使用内核对象,先用CreateEvent调用;初始nUsageCount为1;
2) OpenEvent或CreateEvent都可以获取内核对象的句柄;
3) 内核对象状态(是否受信)的改变;
BOOL WINAPI SetEvent(__in HANDLE hEvent);
BOOL WINAPI ResetEvent(__in HANDLE hEvent);
4) 对于自动重置类型的内核对象,当在这样的内核对象上使用WaitForSingleObject返回时,自动重置为未受信状态;
现在使用内核对象实现跟临界区同样的效果:UINT WINAPI ThreadProc1(LPVOID lpParam)
{
while(bRet)
{
::WaitForSingleObject(hEvent,INFINITE);
gCount1++;
gCount2++;
::SetEvent(hEvent);
}
return 0;
}
hEvent = ::CreateEvent(NULL,FALSE,TRUE,NULL);
四. 信号量内核对象
信号量内核对象允许多个进程同时进入,所以,用CreateSemaphone创建信号量时,要同时给出允许的最大资源记数和当前记数。
常用的函数:CreateSemaphore,OpenSemaphore,ReleaseSemaphore,
信号量的使用特点是更适用于Socket程序中线程的同步。WaitForSingleObject 相当于P操作,ReleaseSemaphone相当于V操作。
对源程序修改,实现同样的功能;
hEvent = ::CreateSemaphore(NULL,1,1,NULL);
UINT WINAPI ThreadProc1(LPVOID lpParam)
{
while(bRet)
{
::WaitForSingleObject(hEvent,INFINITE);
gCount1++;
gCount2++;
::ReleaseSemaphore(hEvent,1,NULL);
}
return 0;
}
五. 互斥内核对象
1. 互斥能够保证多个线程对同一资源的互斥访问。同临界区类似,
2. 常用的函数有:CreateMutex,OpenMutex ,ReleaseMutex,WaitForSingleObject,WaitForMultipleObjects。
对源程序修改,实现同样的功能:
hEvent = ::CreateMutex(NULL,NULL,NULL);
UINT WINAPI ThreadProc1(LPVOID lpParam)
{
while(bRet)
{
::WaitForSingleObject(hEvent,INFINITE);
gCount1++;
gCount2++;
::ReleaseMutex(hEvent);
}
return 0;
}