注:这是好早就发资源上的了,现在看起来似乎有更好的方法,先发帖吧,有兴趣的可讨论再做修改。
文档下载:
http://download.csdn.net/download/guestcode/3022300
自扩充的Lock-Free并发环形队列算法
卢益贵 中国广西南宁市
QQ:48092788 E-Mail: [email protected] Blog:http://blog.csdn.net/guestcode
(说明:因近段时候太多事情需要忙碌,自第一次起草到现在已经过了2个月了均未能成稿,只能简单描述了,暂时先占个坑先。部分阅读者看起来可能感觉吃力,相关资料可以搜索“Lock-Free Queue”关键词,阅读Lock-Free的相关文档后再阅读本文。2011-2-14 23:00)
摘要:此处省略512个字。
关键词:
自动扩充;锁无关;环形链表;并发环形队列
Auto Extern;Lock-Free;Circular Linked-List;ConcurrentCircular Queue
1、引言
环形链表具有避免申请释放节点元素的优点,但为了减少资源使用所以不能分配无穷大的节点数,只能根据具体需求分配一定数量的节点数,正因为如此,可能由于对系统分析不足而导致初始化的节点数不一定能满足队列的容量需求,这个时候就需要队列具有自适应的扩充队列节点数的功能,基于锁(Lock-Base)的算法实现扩充是相当容易的,但基于锁无关(Lock-Free)实现起来难度较大,下面将使用伪代码介绍基于Lock-Free实现可自适应扩充的环形并发队列算法,并提出了优化方案以使特定环境下能再提高队列效率。
2、类型定义(type define)
struct
{
int tag;
unsigned int atr;
}Data;
struct
{
int tag;
Node *ptr;
}Point;
struct
{
Node *next;
Data data;
}Node;
3、变量定义(var define)
Point head, tail;
4、初始化(init)
Init()
{
n1 = NewNode ();
n2 = NewNode ();
n3 = NewNode ();
//atr = 0表示该节点为空,非0为入列数值
n1.data.atr=n2.data.atr=n3.data.atr=0;
//三个节点组成环形链表
n1.next=n2;
n2.next=n3;
n3.next=n1;
head=tail=n1;
}
说明:tail永远指向空节点(atr=0),head.ptr不等于tail.ptr和head.ptr.data.atr不为0为队列非空的条件。
5、插入节点(insert node)
BOOL InserNode(Node *t,*n)
{//t和n之间插入一个节点
Node *new = NewNode();
if(!new)
return(FALSE);
new.data.atr = 0;
new.next = n;
if(!CAS(&t.next, n, new))
FreeNode(new);
return(TRUE);
}
6、入列(enqueue)
BOOL Enqueue(const TType data)
{//TType为任意32位的数值类型
Point OldTail;
Data OldData;
//0不能入列
if(0 == (unsignedint)data)
return(FALSE);
for(;;)
{
OldTail = tail;
OldData = OldTail.ptr.data;
if(OldTail != tail)
continue;
if(OldData != OldTail.ptr.data)
continue;
//追上头了
if(OldTail.ptr.next == head.ptr)
{
//在头和尾之间插入一个节点
if(InsertNode(OldTail.ptr,OldTail.ptr.next)
continue;
//插入失败
return(FALSE);
}
//如果被占用了,我来移动队列尾指向下一个节点吧
if(OldData.atr)
{
CAS(&tail, OldTail,<OldTail.ptr.next, OldTail.tag + 1>);
continue;
}
//写入数据到队列尾
if(!CAS(&OldTail.data,OldData, <data, OldData.tag + 1>)
continue;
//成功则队列尾移动到下一个节点
CAS(&tail,OldTail, <OldTail.ptr.next, OldTail.tag + 1>);
return(TRUE);
}
}
7、出列(dequeue)
BOOL Dequeue(TType&data)
{//TType为任意32位的数值类型
Point OldHead;
for(;;)
{
OldHead = head;
if(OldHead != head)
continue;
//头尾相等和atr=0表示队列为空
(a)if((OldHead.ptr== tail.ptr) || (!OldHead.data.atr))
return(FALSE);
//头下移
(b)if(!CAS(&head,OldHead, <OldHead.ptr.next, OldHead.tag + 1>)
continue;
//移成功,该节点被我(线程)独享,可以放心的读出数据和设置该节点为空了
(c)data= OldHead.ptr.data.atr;
(d)OldHead.ptr.data.atr= 0;
return(TRUE);
}
}
8、缺点和优化
本队列的缺点是入列的数据不能为0,解决的办法是将数据结构Data改为:
Struct
{
shorttag;
shortflag;
unsignedint atr;
}Data;
上面数据结构大小保持为64位,flag为0表示该节点为空,非0为该节点有数据,那么atr值即可为32位范围内的任意值,修改出入列代码中相关的判断语句即可。
另外,多次CAS也会降低性能,因此入列函数可以优化为一次CAS即可完成:
先修改Node结构(那么Data结构就可以省去了,可以修改出列相关代码):
struct
{
Node*next;
unsignedint data;
}Node;
优化后的入列代码:
BOOL Enqueue(const TType data)
{
Point OldTail;
if(0 == (unsigned int)data)
return(FALSE);
for(;;)
{
OldTail= tail;
if(OldTail!= tail)
continue;
//追上头了;或者下个节点还没有被置为空,则插入一个节点,防止重叠
(e)if((OldTail.ptr.next == head.ptr) || (OldTail.ptr.next.data))
{
if(InsertNode(OldTail.ptr, OldTail.ptr.next)
continue;
return(FALSE);
}
//先将尾下移
(f)if(!CAS(&tail, OldTail, <OldTail.ptr.next,OldTail.tag + 1>))
continue;
//如果成功,那么我就可以独享该节点,放心的写入数据吧
(g)OldTail.ptr.data = data;
return(TRUE);
}
}
注意:
优化后的入列函数在(f)和(g)之间,当前线程有可能会失去量程,导致该节点(g)未能先写入数据,而后面的的节点(OldTail.ptr.next)被别的线程先写入数据,造成队列为空(a)的假象,当然是几率问题了。尽管有缺陷,但可以用在特定环境下,比如在多核(CPU)指定内核(CPU)运行特定的单一线程,即可避免分时的几率就不会有失去量程的可能性。
同样,在出列函数的(b)和(c)之间也有一样的问题,但优化后的入列函数因为不会因为出列函数失去量程而导致for(;;)死等待到(d),因为优化后的入列函数(e)处有了判断,会插入一个节点,避免下移导致重叠确保tail永远指向空的节点。
9、结论
此处省略2048个字。
10、参考资料
此处省略1024个字。