zeromq源代码分析5-2------管道相关的数据结构yqueue, ypipe, pipe等

本文我们来讲一下ypipe的实现。

其实ypipe就是上一篇文章中提到的yqueue的一个为lock-free的封装加强实现。

ypipe主要有写入write(), 读取read(), flush()的功能。当一个元素被写入管道的时候,如果不调用flush()函数,那么是不能从该管道中读取出来的。由于zeromq读写操作不在同一个线程,因此该类就采用了CAS的原子操作来完成刷新和读取时检查的操作,这样该类就能保证线程安全,所以称为所谓的lock-free。

让我们说明一下这几个指针的作用:

 //  Points to the first un-flushed item. This variable is used
        //  exclusively by writer thread.
        T *w; // 指向第一个未刷新的元素

        //  Points to the first un-prefetched item. This variable is used
        //  exclusively by reader thread.
        T *r; // 指向第一个未预取的元素

        //  Points to the first item to be flushed in the future.
        T *f; // 指向即将刷新的元素,其实从代码流程上来看应该是即将被刷新的元素的后面一个。。。

        //  The single point of contention between writer and reader thread.
        //  Points past the last flushed item. If it is NULL,
        //  reader is asleep. This pointer should be always accessed using
        //  atomic operations.
        atomic_ptr_t <T> c; //读写线程共享的原子操作元素的指针。主要就完成刷新的工作的

基本工作流程如下:

1. ctor(构造函数): 创建一个队列,push一个terminator元素,r, w, f, c都指向terminator元素。

2. write(2)函数: push一个元素V到队列中,并将f置向即将要刷新的元素的下一个,这儿就是iterminator元素。

3. flush()函数: 将c原子操作设置成f并将w指向f,表明刷新。

4. read(1)函数: 会调用check_read()函数,如果返回为true就pop元素。而check_read()元素会将r原子操作设置成c,这样读线程就能不断地pop元素,直到遇到r指针的元素。


我们再来深入分析下这几个函数:

1. 构造函数:

        inline ypipe_t ()                                                                                                     
        {   
            //  Insert terminator element into the queue.                                                                     
            queue.push ();                                                                                                    
            
            //  Let all the pointers to point to the terminator.
            //  (unless pipe is dead, in which case c is set to NULL).                                                        
            r = w = f = &queue.back ();                                                                                       
            c.set (&queue.back ());                                                                                           
        }                                                                                   
注意其中队列中插入了一个terminator元素。最开始r, w, f都是指向该元素的,同样c也是。

2. write(2):

        //  Write an item to the pipe.  Don't flush it yet. If incomplete is
        //  set to true the item is assumed to be continued by items
        //  subsequently written to the pipe. Incomplete items are never
        //  flushed down the stream.
        inline void write (const T &value_, bool incomplete_)
        {   
            //  Place the value to the queue, add new terminator element.
            queue.back () = value_;
            queue.push (); 

            //  Move the "flush up to here" poiter.
            if (!incomplete_)
                f = &queue.back (); 
        }   

这边的第二个参数incomplete_如果为false的时候f才会设置,才有机会被刷新。

3. flush():

        //  Flush all the completed items into the pipe. Returns false if
        //  the reader thread is sleeping. In that case, caller is obliged to
        //  wake the reader up before using the pipe again.
        inline bool flush ()
        {
            //  If there are no un-flushed items, do nothing.
            if (w == f)
                return true;

            //  Try to set 'c' to 'f'.
            if (c.cas (w, f) != w) {

                //  Compare-and-swap was unseccessful because 'c' is NULL.
                //  This means that the reader is asleep. Therefore we don't
                //  care about thread-safeness and update c in non-atomic
                //  manner. We'll return false to let the caller know
                //  that reader is sleeping.
                c.set (f);
                w = f;
                return false;
            }

            //  Reader is alive. Nothing special to do now. Just move
            //  the 'first un-flushed item' pointer to 'f'.
            w = f;
            return true;
        }
1. 如果w == f, 表明目前木有未刷新的items,就直接返回true。

2. 使用c的原子操作CAS来将c设置成f。CAS就是compare and swap,这个例子来说即如果c == w就交换c和f,返回c原先的值,而该操作是个原子操作,可以看具体实现,在这我就先不讲了。PS: 不同平台不一样的。。。 这样如果成功了c就指向了第一个未刷新的元素的位置了。

3. 最后将w置成f, 表明目前真的木有刷新的items了。


4. check_read():

//  Check whether item is available for reading.
        inline bool check_read ()
        {
            //  Was the value prefetched already? If so, return.
            if (&queue.front () != r && r)
                 return true;

            //  There's no prefetched value, so let us prefetch more values.
            //  Prefetching is to simply retrieve the
            //  pointer from c in atomic fashion. If there are no
            //  items to prefetch, set c to NULL (using compare-and-swap).
            r = c.cas (&queue.front (), NULL);

            //  If there are no elements prefetched, exit.
            //  During pipe's lifetime r should never be NULL, however,
            //  it can happen during pipe shutdown when items
            //  are being deallocated.
            if (&queue.front () == r || !r)
                return false;

            //  There was at least one value prefetched.
            return true;
        }
该函数在read(1)函数中pop元素之前被调用。 其中r表明第一个不能读的元素,也就是在r之前的元素都已经预取出来,可以被reader读取。

1. 如果还有预取的元素,就返回true。

2. 否则就预取元素,将r通过CAS原子操作置为c。

3. 如果预取不到元素,返回false。

4. 预取元素成功后返回true。


后面一篇文章5-3我们会将pipe,敬请期待。

希望有兴趣的朋友可以和我联系,一起学习。 kaka11.chen@gmail.com

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值