lock free 实现的多线程链表通常无法避免 ABA问题。ABA 问题的实质就是我们刚释放的内存可能会被马上又分配出来,被其他线程又放入到链表里了,导致Interlock函数判断链表节点没有改变(实际上节点已经被删除过一次了,链表发生了改变),而导致错误。
那么解决方法最有3中: 一种不使用 interlock函数,一种就是在内存中增加计数标志,一种就是不释放内存。
不使用 Interlock 肯定不现实;),通常认为非对称内存服务器下interlock 的效率比较高,重要的可能是interlock函数不会导致程序停顿,就是每一步都有进展 ,这样就不可能发生死锁。
增加计数标志,原本在interlock中我们比较内存地址, 使用了计数标志后,新分配的节点比较对这个计数器+1。这样比较内存地址相同的节点时,我们再同时多检查一次计数器。这样就不会把新添加的节点误会作已有的旧节点了。不过因为要同时判断地址和计数标志,这个方案实现较复杂。
这里我采用了不释放内存这个方案。就是在链表的节点释放时, 将节点放到内存队列尾部里,在申请节点内存时,从内存队列头部取出被释放的内存。 这里有一个问题:
如果内存队列中保存的释放节点数太少的话,就回出现刚释放的内存立刻又被分配出去了。所以, 我们再分配内存的时候, 必须保证内存队列内节点有一定的长度,这样被分配出去的内存节点可能是很久前被释放的内存。 那么这个长度多少合适呢?其实应该节点数超过线程数量1倍应该就是安全的了。因为只需要保证竞争的线程间不会使用同样的节点即可。
实现代码如下:
template<class T>
class LinkQueue{
public:
typedef bool (*pfnFindCompare)(T a,T b);
typedef NodeLinkGC<Node<T>> LinkGC;
private:
Node<T>* m_pHead;
Node<T>* m_pTail;
Node<T> *m_pEmpty;
LinkGC* m_pGC;
public:
LinkQueue()
:m_pEmpty(NULL),
m_pHead (NULL),
m_pTail(NULL),
m_pGC(NULL)
{
m_pEmpty = new Node<T>;
m_pHead = m_pEmpty;
m_pTail = m_pEmpty;
m_pGC = new LinkGC(1024);
}
virtual ~LinkQueue()
{
if(m_pEmpty)
delete m_pEmpty;
}
int Push(T ele)
{
Node<T> * pAdd =m_pGC->Allocate(); //通过内存GC来分配
pAdd->pNext = NULL;
pAdd->ele = ele ;
while(InterlockedCompareExchangePointer((volatile PVOID *)&m_pTail->pNext,pAdd,NULL)!=NULL){};
return 0;
}
bool Find(T ele)
{
if(m_pHead == m_pTail)
return FALSE;
Node * ph = m_pHead->pNext;
Node * pe= m_pTail;
while(ph!=pe)
{
if(ph->ele == ele)
return true;
else
ph=ph->pNext;
}
return false;
}
bool FindCompare(T& ele,pfnFindCompare pfn)
{
if(m_pHead == m_pTail)
return FALSE;
Node<T> * ph = m_pHead->pNext;
Node<T> * pe= m_pTail;
while(ph!=pe)
{
if(pfn(ph->ele,ele))
{
ele = ph->ele;
return true;
}else
ph=ph->pNext;
}
return false;
}
int Delete(T ele)
{
NumberTaking a;
if(m_pHead==m_pTail)
return -1;
Node<T> * ph = m_pHead->pNext;
Node<T> * pe = m_pTail;
Node<T>* pPre =m_pEmpty;
LinkGC* pGc = LinkGC::CreateInstance();
while(ph!=pe)
{
if(ph->ele == ele)
{
if(pPre==m_pEmpty)
{
Node<T>* pt= PopNode();
pGc->Release(pt);
return 0;
}
else
{
pPre->pNext = ph->pNext;
delete ph;
pGc->Release(ph);
return 0;
}
}
else
{
pPre = ph;
ph=ph->pNext ;
}
}
return -1;
}
int Pop(T & ele)
{
Node<T>* pNode = PopNode();
if(!pNode)
return -1;
ele = pNode->ele ;
m_pGC->Release(pNode);
return 0;
}
private:
Node<T>* PopNode()
{
Node<T>* pOld=NULL ;
Node<T> *pNext =NULL;
do{
pOld=m_pHead->pNext;
Node<T> *pNext = pOld->pNext;
}
while(InterlockedCompareExchangePointer((volatile PVOID*)&m_pHead->pNext,pNext,pOld)!=pOld);
return pOld;
}
};
内存收集器:
template<class T>
class NodeLinkGC
{
int m_iLimitCount ;
RingQueue<T*> *m_pQueue;
NodeLinkGC()
{}
public:
/*
*/
explicit NodeLinkGC(const unsigned int sz,const int limitCount=128)
{
m_pQueue = new RingQueue<T*>(sz);
if(m_pQueue ==NULL)
{
throw "NodeLinkGc construct error";
}
m_iLimitCount = limitCount;
}
/*
这个可能还用不上
*/
static NodeLinkGC<T> * CreateInstance(const int sz,const int limitSize )
{
static volatile NodeLinkGC<T> * g_pGC=NULL;
NodeLinkGC<T> * pGC = NULL;
if(g_pGC ==NULL)
{
try{
pGC = new NodeLinkGC<T>(sz,limitSize);
}
catch(char* pError)
{
OutputDebugStringA(pError);
return NULL;
}
if(pGC ==NULL)
{
OutputDebugStringA("NodeLinkGc createInstance failed");
return NULL;
}
if(::InterlockedCompareExchangePointer((volatile PVOID*)&g_pGC,pGC,NULL) !=NULL)
{
delete pGC ;//如果交换失败,证明有其他线程将pGC 已经分配了,所以我们要释放自己的内存
}
}
return (NodeLinkGC<T>*)g_pGC;
}
T* Allocate()
{
if(m_pQueue)
{
//m_iLimitCount的判断,可以确保被释放到GC 的内存被保留足够的时间 ,以避免ABA 问题
if((m_pQueue->IsEmpty())|| m_pQueue->GetCount()>m_iLimitCount)
return new T;
else
{
T* p=NULL;
int ret = m_pQueue->Pop(p);
if(ret==0)
return p ;
else
{ //发生这种情况通常是队列里node数量太少,如果大量线程一直申请的话,
// 容易发生有部分线程一直没拿到内存的情况,所以这里不对m_pQueue做严格的分配
//即使m_pQuque里有几个node ,为保证分配成功,也还是会new
return (new T);
}
}
}
return NULL;
}
int Release(T * p)
{
if(p)
{
if(0==m_pQueue->Push(p))
return 0;
}
return -1;
}
};