线程同步

同步可以保证在同一时刻只有一个线程对某个共享资源有控制权。共享资源包括全局变量、公共数据成员或句柄等。

一.临界区对象实现同步。

(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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值