从零开始学习EasyDarwin之无锁队列

本文深入探讨了EasyDarwin项目中的无锁队列实现细节,包括其核心数据结构——环形数组的设计,以及入队列和出队列的具体操作流程。通过对比加锁队列,阐述了无锁队列的优势及其应用场景。

最近1.2天都在研究EasyDarwin的无锁队列 
参考资料如下: 
**http://coolshell.cn/articles/8239.html 
陈皓的博客,讲解了CAS,Compare & Set,或是 Compare & Swap的知识,使用链表的无锁队列的实现,以及可能面临的ABA问题。很不错的一篇文章,强烈推荐下.**

**http://www.codeproject.com/Articles/153898/Yet-another-implementation-of-a-lock-free-circular 
codeproject上一篇讲解lock-free queue 的文章,图文并茂的,挺容易懂的,前提是英文.**

在网络服务器中,往往需要对数据进行高并发的读写操作。最原始的方式是对数据缓冲队列进行加锁(Mutex Lock),以完成的线程间的同步与互斥。 
但操作系统对资源进行加锁与解锁需要耗费很多时间,对于时间要求比较高或者要求迅速响应的服务器,锁的方式很有可能不能满足系统设计的要求。 
于是寻求设计一种不加锁也可以完成互斥访问的数据缓冲队列。 
所谓的无锁,我理解的是采用原子操作或者使用特定的数据结构来实现数据的互斥访问.

网上搜索我看了好久资料(包括资料和代码实现),智商平平的我才摸清楚无锁队列的静脉. 
对无锁队列的原理了解7788后,我们一起理一理EasyDarwin中的无锁队列 
主要代码都在:OSQueue.cpp和OSQueue.h

int iread_pos;//当前读的位置 
int iwrite_pos;//当前写的位置 
int ivalid_elems;//数组中元素的个数 
int curId;//当前id 
CyclicElem elems[MAX_QUEUE_ELEMS];//环形数组(重点哦,认真点)

第一部分: 入队列

CyclicQueue::EnQueue(OSQueueElem* object)函数解析

int  EnQueue(OSQueueElem* object)
    {
        if(ivalid_elems == MAX_QUEUE_ELEMS || object == NULL)
        {
            //EnQueue failed full
//          printf("EnQueue failed reason 1\n");
            return -1;
        }
//      printf("iwrite_pos== %d iread_pos == %d\n",iwrite_pos,iread_pos);
        if(iwrite_pos < 0 || iwrite_pos >= MAX_QUEUE_ELEMS || iwrite_pos + 1 == iread_pos)
        {
//          printf("EnQueue failed reason 2\n");        
            return -1;
        }

        elems[iwrite_pos].elem = object;
        elems[iwrite_pos].valid = true;
        iwrite_pos ++;
        if(iwrite_pos == MAX_QUEUE_ELEMS)
        {
            iwrite_pos = 0;
        }
        ivalid_elems ++;
        return 0;
//printf("insert iwrite_pos == %d ivalid_elems[%d] %d\n",iwrite_pos,ivalid_elems,curId);
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

入队列时需要判断下iwrite_pos在环形数组的范围之内,并且iwrite_pos + 1 != iread_pos,如果iwrite_pos + 1 == iread_pos则表示用户想写入数据的位置刚好是另一个用户读取数据的位置,违反互斥性.

写入数据前如图: 
这里写图片描述

elems[iwrite_pos].elem = object;//写入数据内容 
elems[iwrite_pos].valid = true;//每次写入数据,设置valid = true,表示有数据,读写数据后,设置valid = false,表示无数据

写入数据后如图: 
这里写图片描述 
从图中可以看出: 
写入位置向后移动,即iwrite_pos++ 
数据中的元素个数+1,即ivalid_elems ++;

第二部分: 出队列

CyclicQueue::DeQueue()函数

OSQueueElem* DeQueue()
    {
        OSQueueElem *object = NULL;

        //当前队列是否为空
        if(ivalid_elems == 0)
        {
            return NULL;
        }

        //iread_pos是否符合环形数组范围
        if(iread_pos < 0 || iread_pos >= MAX_QUEUE_ELEMS)
        {
            return NULL;
        }

        //判断数据有效性
        if(elems[iread_pos].valid == true)
        {
            //读取元素
            object = elems[iread_pos].elem;
        }

        //将读取过的位置重置为无值状态,值为NULL
        elems[iread_pos].valid = false;
        elems[iread_pos].elem = NULL;
        iread_pos ++;
        if(iread_pos == MAX_QUEUE_ELEMS)
        {
            iread_pos = 0;
        }
        ivalid_elems --;
//printf("got iread_pos == %d ivalid_elems[%d] %d\n",iread_pos,ivalid_elems,curId);
        return object;
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

出队列之前如图: 
这里写图片描述

读取一个元素,出队列之后如图: 
这里写图片描述

在读取一个元素出队列后,队列为空,如图: 
这里写图片描述

对应入队列,出队列的变化为: 
读取位置向后移动,即iread_pos++ 
数据中的元素个数-1,即ivalid_elems –;

在什么地方使用CyclicQueue的EnQueue和DeQueue?

class OSQueue_Blocking
{
    public:
        OSQueue_Blocking() {}
        ~OSQueue_Blocking() {}

        OSQueueElem*    DeQueueBlocking(OSThread* inCurThread, SInt32 inTimeoutInMilSecs);
        OSQueueElem*    DeQueue();//will not block
        void            EnQueue(OSQueueElem* obj);

        OSCond*         GetCond()   { return &fCond; }
//        OSQueue*        GetQueue()  { return &fQueue; }
        CyclicQueue*    GetQueue()  { return &fQueue; }           
    private:

        OSCond              fCond;
        OSMutex             fMutex;
//        OSQueue             fQueue;
        CyclicQueue         fQueue;
};
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在OSQueue_Blocking类中使用CyclicQueue的EnQueue和DeQueue函数入队列和出队列 
其中CyclicQueue fQueue;是锁定的成员变量

void OSQueue_Blocking::EnQueue(OSQueueElem* obj)
{
    {
        OSMutexLocker theLocker(&fMutex);
        fQueue.EnQueue(obj);
    }
    fCond.Signal();
}
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在Task入队列以后,调用fCond.Signal();发送一个信号量,表明fQueue队列中加入了Task,可以获取Task了.

在OSQueue_Blocking::DeQueueBlocking(OSThread* inCurThread, SInt32 inTimeoutInMilSecs)函数中 
有几句代码 
if (fQueue.GetLength() == 0) 

//lock inside, 
OSMutexLocker theLocker(&fMutex); 
fCond.Wait(&fMutex, inTimeoutInMilSecs); 

继而F12进入fCond.Wait函数中 
DWORD theErr = ::WaitForSingleObject(fCondition, theTimeout); 
一直在等待fCondition变为有信号状态(或者指定超时时间theTimeout),然后获取队列中之前投递的Task任务.

分析代码,我的一点小心得,跟着一条线,一个关键变量或者函数,全局或者匹配搜索,然后看看都什么地方调用了,是怎么玩的,心里多猜想可能是怎么回事,然后在阅读代码的过程中一点点去验证.

0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sunxiaopengsun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值