lcok free 的单写多读循环内存从无锁的单写单读循环内存变化而来。所以这个问题分2节来介绍:
. 单写单独 循环内存
. lock free 单写多读循环内存
1. 常规的单写单读循环内存通过设置读指针和写指针保证了线程的安全。 其实现如下:
写数据到内存里(m_uWritePos 声明为volatile ,m_iSize 为内存数组大小)
unsigned int CircleBuffer::SetBuf(char* buffer, unsigned int len)
{
if(GetRemainSize()<len)//已经使用的内存
return 0;
unsigned int l=0;
len = __min(len, m_iSize - m_uWritePos + m_uReadPos);
l = __min(len, m_iSize - (m_uWritePos & (m_iSize - 1))); // & 用来取模
memcpy(m_pBuf + (m_uWritePos & (m_iSize - 1)), buffer, l);
memcpy(m_pBuf, buffer + l, len - l);
m_uWritePos += len;
return len;
}
读数据出内存 (m_uReadPos 声明为volatile )
unsigned int CircleBuffer::GetBuf(char* buffer, unsigned int len)
{
unsigned int l;
if(GetUsedSize()<len) //已经使用的内存
return 0;
len = __min(len, m_uWritePos - m_uReadPos);
l = __min(len, m_iSize- (m_uReadPos & (m_iSize - 1)));
memcpy(buffer, m_pBuf + (m_uReadPos & (m_iSize - 1)), l);//复制后面,
memcpy(buffer + l, m_pBuf, len - l); // 复制前面
m_uReadPos += len;
return len;
}
原理: 线程写的时候, m_uWritePos 与到 m_uReadPos 的距离为空闲内存,读的时候,m_uReadPos到m_uWritePos为已使用内存。读写pos 都往一个方向运动,所以读写同时进行时,数据访问不会交叉。
缺点: 这里线程同步通过自动竞争进行, 最坏情况就是写线程写数据内存空间不足,函数返回错误,但读线程读内存刚刚移动了m_uReadPos ,实际上内存空间是可以进行写入的。但这个并不影响多线程的安全性。改进的方法是自旋一段时间:
unsigned int CircleBuffer::SetBuf(char* buffer, unsigned int len)
{
srand(GetTickCount()):
int count = 10+rand()%4;
while(GetRemainSize()<len && count)
{
if(--count>0)
YieldProcessor();
}
if(count<=0)
return 0;
unsigned int l=0;
len = __min(len, m_iSize - m_uWritePos + m_uReadPos);
l = __min(len, m_iSize - (m_uWritePos & (m_iSize - 1))); // & 用来取模
memcpy(m_pBuf + (m_uWritePos & (m_iSize - 1)), buffer, l);
memcpy(m_pBuf, buffer + l, len - l);
m_uWritePos += len;
return len;
}
2. 单写多读
既然读与写线程不会互相干扰,那么多读只需要在读线程之间做好互斥工作即可。 其实现如下:
unsigned int CircleBuffer::GetBuf(char* buffer, unsigned int len)
{
unsigned int l;
srand(GetTickCount()):
int count = 10+rand()%4;
while(GetUsedSize()<len && count)
{
if(--count>0)
YieldProcessor();
}
if(count<=0)
return 0;unsigned int newPos = 0;
unsigned int oldPos = 0;
do{
oldPos = m_uReadPos;
newPos = oldpos + len;
if(GetUsedSize()<len) // 这里我们要循环判断,因为多个读线程在竞争,如果别的线程已经读走了一段,剩下的长度不够了,你就不可以读了
return 0 ;
len = __min(len, m_uWritePos - oldPos);
l = __min(len, m_iSize- (oldPos & (m_iSize - 1))) ;
memcpy(buffer, m_pBuf + (m_uReadPos & (m_iSize - 1)), l);//复制后面,
memcpy(buffer + l, m_pBuf, len - l); // 复制前面
}while( InterlockedCompareExchange(&m_uReadPos,newPos,oldPos) !=oldPos);// 循环比较
return len;
}
我们再InterlockedCompareExchange 失败后,必须再次进行 GetUsedSize 判断,因为m_uReadPos已经被其他线程修改了,所以剩余内存的大小就得重新判断。
声明如下:
class CircleBuffer
{
public:
CircleBuffer(int sz);
~CircleBuffer(void);
unsigned int CircleBuffer:: GetRemainSize();
unsigned int CircleBuffer::GetUsedSize();
unsigned int CircleBuffer::SetBuf(char* buffer, unsigned int len);
unsigned int CircleBuffer::GetBuf(char* buffer, unsigned int len);
unsigned int CircleBuffer::LookBuf(char* buf,unsigned int len);
private:
int m_iSize;
char* m_pBuf;
volatile unsigned int m_uWritePos;
volatile unsigned int m_uReadPos;
};