一種多線程基於計數無鎖實現

      本文介紹一種不加鎖,不使用原子操作的多線程同步機制。先申明下,該方案爲我在實際編程中創造出來的,事先我沒有在其中地方看到關於該方案的介紹。

     在多線程編程中,我們經常會遇到線程同步問題,這時候加鎖就變得必不可少。但是鎖的使用會或多或少帶來某些性能上的下降。下面先介紹一個多線程編程中經常遇到的問題模型,然後實現一種無鎖解決方案。


     問題模型:
     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;
}


發佈了12 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章