本文介紹一種不加鎖,不使用原子操作的多線程同步機制。先申明下,該方案爲我在實際編程中創造出來的,事先我沒有在其中地方看到關於該方案的介紹。
在多線程編程中,我們經常會遇到線程同步問題,這時候加鎖就變得必不可少。但是鎖的使用會或多或少帶來某些性能上的下降。下面先介紹一個多線程編程中經常遇到的問題模型,然後實現一種無鎖解決方案。
問題模型:
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;
}