欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]一种多线程基于计数无锁实现

[原创]一种多线程基于计数无锁实现

先申明下,该方案为我在实际编程中创造出来的,事先我没有在其中地方看到关于该方案的介绍。
     在多线程编程中,我们经常会遇到线程同步问题,这时候加锁就变得必不可少。但是锁的使用会或多或少带来某些性能上的下降。下面先介绍一个多线程编程中经常遇到的问题模型,然后实现一种无锁解决方案。
     问题模型:
     R:表示某种资源,线程A往R中存放资源,线程B从R中取出资源。
    先看看常规解决方法:
    线程A往R中存放资源
  1.获取锁(此处可能睡眠)。
  2.存入资源。
  3.修改资源计数。
  4.释放锁。
    线程B从R中取出资源
  1.获取锁(此处可能睡眠)。
  2.取出资源。
  3.修改资源计数。
  4.释放锁。
    下面针对该模型实现一种无锁的解决方案:
    首先定义一个数组ARRAY存在资源,假设数组的长度为L,然后再定义两个变量READ和WRITE。READ表示读计数,WRITE表示写计数。
    该方案的基本思想为:
    1.存入资源增加WRITE。
    2.读取资源增加READ。
    3.WRITE和READ都只增不减。
    4.判断ARRAY存在空余空间,WRITE - READ < L。
    5.判断ARRAY为空,WRITE = READ 。
    6.定位读位置READ%L,定位写位置WRITE%L。
实际操作流程为:
初始化READ和WRITE为0。
线程A往R中存放一个资源
  1.判断数组中的资源未满。
  2.存放资源到ARRAY[WRITE%L]
  3.增加WRITE。
  线程B往R中读取一个资源
  1.判断数组中的资源不为空。
  2.存放资源到ARRAY[READ%L]
  3.增加READ。
   可能大家已经看出,上面的实现存在一个严重的问题,就是越界的问题,下面讨论解决方案:
    1.越界后WRITE - READ需要保证正确。
    大家知道无符号数有一个特性,
    0x00000000-0xffffffff = 1;
    0x00000000-0xfffffffe = 2;
    只要把READ和WRITE定义成无符号数,就能保证WRITE - READ在越界后保证正确性。
   2.WRITE%L 和 READ%L在越界后的正确性,我们需要保证以下等式成立:
0xffffffff%L = L -1
      为了保证以上等式成立,可以将L设成2的n次方,对应32位整数,n的取值范围为0~31. 由于限定L为2的n次方,WRITE%L 和 READ%L可以写成WRITE&(L-1) 和 READ%L&(L-1).
    讨论:
     该无锁实现对多线程编程常用的模型提出一种无锁实现,但在使用中还需注意一下几点:
    1.为防止程序从高速缓存中取值,必须将变量READ和WRITE定义成volatile类型。
    2.该方案要求缓冲区的长度为2的n次方,取值可以为1,2,4,8,16,32,64……,大部分时候可以满足应用上的需求。
    3.该方案目前只适用于基于数组的缓冲区结构。
    4.该方案目前只适一个读者,一个写者的情形,如果存在多个读者,多个写者,需要分别对读者和写者进行加锁,但是使用该方案还是可以减少锁的力度。

#include "stdafx.h"
#include <windows.h>
class ZwAsynCount
{
public:
	ZwAsynCount(unsigned uSize)	//uSize必须为2的n次方
	{
		m_uReadCount = 0;
		m_uWriteCount = 0;
		m_uSize = uSize;
	}
	int Write()					//返回元素位置 -1表示读失败
	{
		int nRet = -1;
		if (m_uWriteCount - m_uReadCount < m_uSize)
		{
			nRet = m_uWriteCount&(m_uSize-1);
		}
		return nRet;
	}
	void AddWrite(int nCount = 1)
	{
		m_uWriteCount += nCount;
	}
	int Read()					//返回元素位置 -1表示写失败
	{
		int nRet = -1;
		if (m_uWriteCount - m_uReadCount > 0)
		{
			nRet = m_uReadCount&(m_uSize-1);
		}
		return nRet;
	}
	void AddRead(int nCount = 1)
	{
		m_uReadCount+= nCount;
	}
private:
	unsigned m_uSize;
	volatile unsigned m_uReadCount;
	volatile unsigned m_uWriteCount;
};
class ZwTestShareBuffer
{
public:
	ZwTestShareBuffer():m_asynCount(128)
	{
	}
	BOOL Read()
	{
		BOOL bRet = FALSE;
		int nPos = m_asynCount.Read();
		if (nPos >= 0)
		{
			printf("read: %d\n",m_data[nPos]);
			bRet = TRUE;
			m_asynCount.AddRead();
		}
		return bRet;
	}
	BOOL Write(int nData)
	{
		BOOL bRet = FALSE;
		int nPos = m_asynCount.Write();
		if (nPos >= 0)
		{
			m_data[nPos] = nData;
			bRet = TRUE;
			m_asynCount.AddWrite();
		}
		return bRet;
	}
private:
	ZwAsynCount m_asynCount;
	int m_data[128];
};
ZwTestShareBuffer TestAsynShareBuffer;
DWORD  WINAPI  WriteProc(LPVOID lpParam)
{
	int nData = 0; 
	while(TRUE)
	{
		if (!TestAsynShareBuffer.Write(nData))
		{
			Sleep(1);
		}
		else
		{
			nData++;
		}
	}
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	::CreateThread(NULL,0,WriteProc,NULL,0,NULL);
	while(TRUE)
	{
		if (!TestAsynShareBuffer.Read())
		{
			Sleep(1);
		}
	}
	return 0;
}

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功