windows编程之应用层的线程同步

当我们进行多线程进行编程的时候呢,确实可以让我们的程序变得更加高效,但是,在面临多线程时,我们必须要做好线程之间的同步。比如说,有一个线程在读内存,另一个线程在写入内存,这将发生不可描述的事件,这就好比一个人在另一个人读书的时候修改书中的文字一样。因此,线程同步就变得相当重要了。好在呢,windows提供了很多的方式可以让线程同步变得容易。下面来介绍下在应用层下的几种线程同步方式。

第一种是自旋锁,Interlocked系列的原子访问操作,什么是原子操作呢?就是不可再分的,不能打断的。也就是说在同一时刻只能有一个在访问同一资源,不能同时进行访问。下面来看一个叫做InterlockedExchange这个API,它是用来将两个数进行交换的一个原子访问操作,其声明如下:

LONG InterlockedExchange(  
  LPLONG Target, // 要交换的变量值
  LONG Value     // 交换后的新值
);
该函数会返回原来的值,因此,在实现自旋锁的时候呢,该函数就变得非常有用,以下是第一种方式测试代码:

#include <windows.h>
#include <list>

//全局变量,关键数据
std::list<int> test = { 1, 2, 3, 4, 5 };

unsigned int  locker = FALSE;  

DWORD WINAPI Thread1(LPVOID lpParameter)
{	
	while (1)
	{
		//将locker的值与TRUE进行交换,返回原来的值
		while (InterlockedExchange(&locker, TRUE) == TRUE) //  #1
			Sleep(0);
		if (test.size())
		{
			printf("%d\n", lpParameter);
			test.pop_front();   // #3
		}
		//再一次进行交换
		InterlockedExchange(&locker, FALSE);//	#2
	}
			
	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
	for (int i = 0; i < 2; i++)
	{
		CreateThread(0, 0, Thread1, (LPVOID)(i + 1), 0, 0);
	}	
	system("pause");
	return 0;
}
当我们将#1和#2注释掉时会发现,程序会崩溃,这是由于当进行#3代码(还没运行完)时,刚好到了另一个线程运行,另一个线程也执行到该代码且运行完,然后CPU时间片到了,原来的线程继续运行,但是该值已经被删除了,所以程序出错了。因此,当我们使用自旋锁时,就能保证该删除操作不会被打断,可以很好的进行线程同步了。

接下来,介绍第二种线程同步的方式----关键段(临界区)。

关键段与自旋锁很像,但还是有些不同,自旋锁它是一直自己把CPU的时间片握在手里(即使没有工作要做),而关键段则是将CPU的时间片贡献出去,让其他的线程进行运行,比如说,你有1分钟上厕所的时间,但是去卫生间的时候发现里边有人,这时你就只能在这里等,但是自旋锁呢,则是直接自己占用卫生间,虽然不能干什么事,直到时间到了,其他人才能继续使用。因此,自旋锁对自身程序来说,是比较有利的,但对操作系统来讲会稍微降低一点性能,而关键段则于此相反。接下来看下几个API:

要使用关键段呢,首先得声明一个CRITICAL_SECTION这个结构体,它就相当于上述例子中的卫生间。我们并不需要该结构体的成员,这里就不再赘述。

当我们声明好CRITICAL_SECTION之后,我们需要给它进行一下初始化,调用InitializeCriticalSection,其声明如下:

VOID InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection  // CRITICAL_SECTION地址
);
初始化完之后,我们就可以将关键数据放在EnterCriticalSection和LeaveCriticalSection之间,将关键数据进行保护起来,它们的声明如下:

VOID EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection  // <span style="font-family: Arial, Helvetica, sans-serif;">CRITICAL_SECTION地址</span>
);

VOID LeaveCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection   // <span style="font-family: Arial, Helvetica, sans-serif;">CRITICAL_SECTION地址</span>
);
当我们使用完关键段后,可以把它释放掉,DeleteCriticalSection用于释放关键段,其声明如下:

VOID DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection   // CRITICAL_SECTION地址
);
以下是关键段的测试代码:

#include <windows.h>
#include <list>

//全局变量,关键数据
std::list<int> test = { 1, 2, 3, 4, 5 };

CRITICAL_SECTION cs;

DWORD WINAPI Thread1(LPVOID lpParameter)
{	
	while (1)
	{
		//进入关键段
		EnterCriticalSection(&cs);
		if (test.size())
		{
			printf("%d\n", lpParameter);
			test.pop_front();
		}
		//离开关键段
		LeaveCriticalSection(&cs);
	}

	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
	//初始化关键段
	InitializeCriticalSection(&cs);
	for (int i = 0; i < 2; i++)
	{
		CreateThread(0, 0, Thread1, (LPVOID)(i + 1), 0, 0);
	}
	
	//DeleteCriticalSection(&cs); //用完之后,释放关键段,这里注释掉是由于主线程将其释放了之后,在运行EnterCriticalSection会出错的
	system("pause");
	return 0;
}
接下来介绍第三种线程同步方式-------读写锁,
读写锁和关键段非常类似,但是与关键段不同的是,读写锁可以分为俩种状态,读和写,当多个线程对关键数据进行读取的时候是允许的,若多个线程对关键数据进行写入时是不行的。除此之外呢,读写锁在性能上是稍强关键段的,我们可以使用读写锁来替代关键段。

在使用读写锁时,应先声明一个SRWLOCK结构,然后调用InitializeSRWLock来进行初始化,声明如下:

VOID WINAPI InitializeSRWLock(
  PSRWLOCK SRWLock<span style="white-space:pre">	</span>//SRWLOCK结构地址
);
之后,我们可以将关键数据放在AcquireSRWLockExclusive和ReleaseSRWLockExclusive之间,由于读写锁有两种方式,因此与之对应的就有两套API,前面介绍的是写的时候的,读取的应该将关键数据放在AcquireSRWLockShared和ReleaseSRWLockShared之间,它们的声明如下:

VOID WINAPI AcquireSRWLockExclusive(
  PSRWLOCK SRWLock    //SRWLOCK 结构地址
);

VOID WINAPI ReleaseSRWLockExclusive(
  PSRWLOCK SRWLock    //SRWLOCK 结构地址
);



VOID WINAPI AcquireSRWLockShared(
  PSRWLOCK SRWLock    //SRWLOCK 结构地址
);

VOID WINAPI ReleaseSRWLockShared(
  PSRWLOCK SRWLock    //SRWLOCK 结构地址
);
读写锁并没有释放函数,它是由操作系统进行释放的,我们不需要进行管理。一下是读写锁的一些测试代码:

#include <windows.h>
#include <list>

//全局变量,关键数据
std::list<int> test = { 1, 2, 3, 4, 5 };

SRWLOCK srw;

DWORD WINAPI Thread1(LPVOID lpParameter)
{	
	while (1)
	{		
		AcquireSRWLockExclusive(&srw);
		if (test.size())
		{
			printf("%d\n", lpParameter);
			test.pop_front();
		}
		ReleaseSRWLockExclusive(&srw);
	}	
	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
	//初始化读写锁
	InitializeSRWLock(&srw);
	for (int i = 0; i < 2; i++)
	{
		CreateThread(0, 0, Thread1, (LPVOID)(i + 1), 0, 0);
	}

	system("pause");
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值