看梁总的题目,实现高速队列,百思不得其解。
后来看了答案,算是比葫芦画瓢实现了一个。
其实最重要的两点:
1。实现一个原子加加,用来得到一个唯一下标。
2.用一个永远不会使用的值当标记,验证该下标对应的位置是否有值。
代码如下:
// 临界锁,线程安全
// 必须要有一个不会用的空值,
template <class T_Key>
class CQQueue_Lockfree
{
public:
CQQueue_Lockfree()
{
m_lMaxQueueSize = -1;
m_tpQueue = NULL;
m_lBegPos = 0;
m_lEndPos = 0;
};
~CQQueue_Lockfree()
{
if(m_tpQueue)
delete m_tpQueue;
};
var_4 InitQueue(var_4 lMaxQueueSize, T_Key null)
{
m_lMaxQueueSize = lMaxQueueSize;
m_tpQueue = new T_Key[m_lMaxQueueSize];
if(m_tpQueue == NULL)
return -1;
m_lBegPos = 0;
m_lEndPos = 0;
m_null = null;
for( var_4 i = 0; i < lMaxQueueSize; i++ )
{
m_tpQueue[i] = null;
}
return 0;
};
void ResetQueue()
{
m_lBegPos = 0;
m_lEndPos = 0;
};
void ClearQueue()
{
if(m_tpQueue)
{
delete m_tpQueue;
m_tpQueue = NULL;
}
m_lMaxQueueSize = -1;
m_lBegPos = 0;
m_lEndPos = 0;
};
void PushData(T_Key tKey)
{
register var_u8 cnt =1;
var_u8 pos = fetch_and_add(&m_lEndPos, cnt)%m_lMaxQueueSize;
while( m_null != m_tpQueue[pos] )
cp_sleep(1);
m_tpQueue[pos] = tKey;
};
T_Key PopData()
{
register var_u8 cnt =1;
var_u8 pos = fetch_and_add(&m_lBegPos, cnt)%m_lMaxQueueSize;
T_Key*p = m_tpQueue+pos;
while( m_null == *p )
cp_sleep(1);
T_Key ret = *p;
*p = m_null;
return ret;
};
private:
var_4 m_lMaxQueueSize;
T_Key* m_tpQueue;
T_Key m_null;
var_u4 m_lBegPos;
var_u4 m_lEndPos;
};
上面是我的代码,梁总的实现更高效,
首先,他认为队列足够长,并且取得足够快,所以在push时,根本不检查队头压队尾的情况。
在pop时,只检查队头-队尾的长度,小于一个阈值就不再取,由于只用于高速队列,会一直有数据被push,所以不会发生阈值以内的数据被延时。
经过测试,这种方式比用锁少了40%。
10亿次放入和读取,10线程花费200秒。
不过梁总测试说8核1.5GHz花了88秒,但在我24核
vendor_id : GenuineIntel
cpu family : 6
model : 45
model name : Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
stepping : 7
cpu MHz : 2300.469
cache size : 15360 KB
的机器上,也只跑了200秒,真是奇怪。