多读者多写者的无锁队列

单读者单写者很容易进行冲突避免,毕竟他们操作的是不同的指针(head和rear),而多读者则需要共同操作head,多写者共同操作rear,似乎必须要加锁,锁机制是内核提供的一种对象,比较影响效率。
可以借鉴spinlock的实现机制,利用CPU提供的原子指令(x86上有cmpxchg,即比较交换,当某个内存地址里的值等于某个值时,则将该地址里的内容与某个寄存器交换,这一系列操作是原子的,由CPU保证),实现CAS原子操作,意为Compare & Set(Swap?)。
 
一种典型的CAS描述如下:
bool compare_and_set(int *reg, int oldval, int newval){

if(*reg==oldval){

*reg=newval;

return true;

}

return false;

}

记住这个CAS操作是原子的,即不可能多个线程通过CAS操作相同的对象。

 
于是,对于入队列可以实现为
void Enqueue(x){

q=new node();

q->value=x;

q->next=NULL;

do{

p=tail;

}while(!CAS(p->next,NULL,q));

CAS(tail,p,q);

}

这种实现有个问题,即只有第一个线程执行了CAS(tail,p,q),其他的线程才能结束循环,继续下一步,而如果第一个线程异常终止或暂停(而没有成功执行CAS操作),则其他所有线程都要死循环。

解决办法是,在do循环中,让线程自己遍历到真正的尾指针,而不是等待tail被更新。
void Enqueue(x){

q=new node();

q->value=x;

q->next=NULL;

p=tail;

do{

while(p->next!=NULL) p=p->next;

}while(!CAS(p->next,NULL,q));

CAS(tail,p,q);

}

如此,即使第一个线程没有执行CAS(tail,p,q),其他线程也可以正常退出循环。

然而,问题又来了,如果多个线程同时到达CAS(tail,p,q),那么只有第一个线程才能正确执行,因为其他的线程此时不满足tail==p,只有等到第一个线程正确执行CAS(tail,p,q)后,才能满足tail==p。遗憾的是,多个线程执行CAS(tail,p,q)的顺序是随机的,如果它们正好按照1,2,3,...的顺序执行,则最终tail可以指向正确的尾指针,如果是...,3,2,1,则前面所有线程的CAS(tail,p,q)都会失败,最后tail指向错误的结尾。
为了在这个地方确定一个顺序,可以给每个线程的q一个编号,该编号即为该节点在链表里的序号,最后CAS(tail,p,q)前,判断当前q的序号是否大于tail的,如果大于,则执行,否则不执行。
void Enqueue(x){

q=new node();

q->value=x;

q->next=NULL;

p=tail;

q->id=p->id+1;

do{

while(p->next!=NULL) {p=p->next;q->id++;}

}while(!CAS(p->next,NULL,q));

do{

p=tail;

}while(q->id>p->id && CAS(tail,p,q));

}

 

posted @ 2013-03-21 15:52  使命召唤  阅读(865)  评论(0编辑  收藏  举报