一、数据结构
Head,Tail是队列的头和尾。
二、基础知识
一般的处理器,有一条指令,一个周期就可以执行,也可以说是原子操作,是不可分割的。
CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的
是 CMPXCHG 汇编指令。有了这个原子操作,我们就可以用其来实现各种无锁(lock free)的数据结构。
利用c语言描述其功能:
bool compare_and_swap (int* accum,int* dest,int newval)
{
if( *accum == *dest ) {
*dest = newval;
return true;
}
return false;
}
还有一些类似的指令:
EnQueue(x)//进队列
{
//准备新加入的结点数据
q = new record();
q->value = x;
q->next = NULL;
do{
p = tail; //取链表尾指针的快照//此时tail是指向固定的值。
}while( CAS(p->next, NULL, q) != TRUE); //如果没有把结点链在尾指针上,再试
CAS(tail, p, q); //置尾结点(6)
}
你会看到,为什么我们的“置尾结点”的操作(第12行)不判断是否成功,因为:
EnQueue(x)//进队列改良版
{
q = new record();
q->value = x;
q->next = NULL;
// 代码中fifo的最后一个元素为tail
p = tail;
oldp = p
do{
while(p->next != NULL)
p = p->next;
}while( CAS(p->next, NULL, q) != TRUE); //如果没有把结点链在尾上,再试
CAS(tail, oldp, q); //置尾结点
}
--------------------------------------------------------------------------------------------------------------
int DeQueue()//出队列
{
do{
p = head;
if(p->next == NULL){
returnERR_EMPTY_QUEUE;
}
while( CAS(head, p, p->next) != TRUE );
return(p->next->value);
}
//个人理解上面的代码有错误
// head 应该为fifo第一个元素
int DeQueue()//出队列
{
do{
p = head;
if(p == NULL)
{
return ERR_EMPTY_QUEUE;
}
while( CAS(head, p, p->next) != TRUE );
return(p->next->value);
}
所谓ABA(见维基百科的ABA词条),问题基本是这个样子:
虽然P1以为变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。ABA问题最容易发生在lock free 的算法中的,CAS首当其冲,因为CAS判断的是指针的地址。如果这个地址被重用了呢,问题就很大了。(地址被重用是很经常发生的,一个内存分配后释放了,再分配,很有可能还是原来的地址)
比如上述的DeQueue()函数,因为我们要让head和tail分开,所以我们引入了一个dummy指针给head,当我们做CAS的之前,如果head的那块内存被回收并被重用了,而重用的内存又被EnQueue()进来了,这会有很大的问题。(内存管理中重用内存基本上是一种很常见的行为)
这个例子你可能没有看懂,维基百科上给了一个活生生的例子——
你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。
这就是ABA的问题。