Deepstream使用udp-json进行数据结构封装

柜员服务营销--孙甜老师

  返回  

libgo基础组件分析(一)

2021/7/21 14:07:39 浏览:

文章目录

  • 侵入式双向链表
    • TSQueueHook
    • SList
      • Attribute
      • Method
        • SList(SList && other)
        • void append(SList &&other)
        • void erase(T *ptr)
        • SList cut(std::size_t n)
      • iterator
    • TSQueue
      • Attribute
      • Method
        • void front(T *&out)
        • void next(T *ptr, T *&out)
        • T *pop()
        • void push(SList && elements)
        • void pushWithoutLock(SList && elements)
        • SList<T> pop_front(uint32_t n)

侵入式双向链表

在libgo中设计了一套侵入式双向链表,用来保存协程任务,调度队列等,所以今天来分析一下这个双向链表的设计,方便我们之后对libgo其他模块的分析。

TSQueueHook

想要这个双向链表,那么链表保存的类型必须继承TSQueueHook,UML类图如下:
在这里插入图片描述

struct TSQueueHook
{   //上一个节点
    TSQueueHook* prev = nullptr;
    //下一个节点
    TSQueueHook* next = nullptr;
    //可选的检查节点
    void *check_ = nullptr;
    //将theNext设置为该节点的下一个节点
    ALWAYS_INLINE void link(TSQueueHook* theNext) {
        assert(next == nullptr);
        assert(theNext->prev == nullptr);
        next = theNext;
        theNext->prev = this;
    }
    //取消与theNext节点的关联
    ALWAYS_INLINE void unlink(TSQueueHook* theNext) {
        assert(next == theNext);
        assert(theNext->prev == this);
        next = nullptr;
        theNext->prev = nullptr;
    }
};

SList

SList是一个类模板,定义时会进行静态断言检测T是否为TSQueueHook的子类,所以一定得先继承TSQueueHook再使用SList

 static_assert((std::is_base_of<TSQueueHook, T>::value), "T must be baseof TSQueueHook");

整体的UML类图如下:
在这里插入图片描述

Attribute

SList的成员变量:

  • head_ : 头节点
  • tail_ : 尾结点
  • count_ : 链表中节点数量

Method

SList(SList && other)

  //移动构造函数
  SList(SList<T> && other)
  {
        //浅拷贝
        head_ = other.head_;
        tail_ = other.tail_;
        count_ = other.count_;
        //将other设为空链表
        other.stealed();
  }

void append(SList &&other)

将一个SList移动进来

  void append(SList<T> &&other)
  {
      //添加进来的SList为空则直接返回
      if (other.empty())
          return;
     //该SList为空
     if (empty())
     {
       //调用移动赋值函数
       *this = std::move(other);
       return;
    }
      //将other的头结点链接到this的尾结点
      tail_->link(other.head_);
      //重新设置尾结点
      tail_ = other.tail_;
      //增加节点数量
      count_ += other.count_;
      //将other设为空链表
      other.stealed();
}

void erase(T *ptr)

删除一个节点,SList并不管理节点的生命周期,erase只是将节点从SList去除,并不会释放节点内存,libgo中有实现一套引用计数体系,若使用了引用计数,则不需要担心生命周期的管理

void erase(T *ptr)
{
      
      if (ptr->prev)
          ptr->prev->next = ptr->next;
      else
      //无prev节点,删除的为head节点
         head_ = (T *)head_->next;
         
      if (ptr->next)
         ptr->next->prev = ptr->prev;
      else
      //无next节点,删除的为tail_节点
         tail_ = (T *)tail_->prev;
      //重置该节点
      ptr->prev = ptr->next = nullptr;
      //节点数量减一
      --count_;
     //引用计数减一,这里使用SFINAE,即使没有引用计数也不会报错
     DecrementRef(ptr);
}

SList cut(std::size_t n)

获取n个节点,以SList返回,获取到的节点数可能小于n

SList<T> cut(std::size_t n)
{
   //链表为空直接返回
   if (empty())
     return SList<T>();
   //n大于该链表节点数,直接将该链表move后返回
   if (n >= size())
   {
      SList<T> o(std::move(*this));
         return o;
   }

   if (n == 0)
   {
     return SList<T>();
   }
   
   SList<T> o;
   auto pos = head_;
   //获取n个节点后pos
   for (std::size_t i = 1; i < n; ++i)
        pos = (T *)pos->next;
   //设置新链表     
   o.head_ = head_;
   o.tail_ = pos;
   o.count_ = n;

   count_ -= n;
   //将链表的头指针设为pos的下一个节点
   head_ = (T *)pos->next;
   pos->unlink(head_);
   return o;
}

iterator

SList也设计了一个简单的迭代器,类似于STL迭代器的设计,这里我们简单过一下,迭代器保存当前节点的指针,上一个节点的指针,下一个节点的指针。

struct iterator
{
        //当前节点
        T* ptr;
        //上一个节点
        T* prev;
        //下一个节点
        T* next;

        iterator() : ptr(nullptr), prev(nullptr), next(nullptr) {}
        iterator(T* p) { reset(p); }
        void reset(T* p) {
            ptr = p;
            next = ptr ? (T*)ptr->next : nullptr;
            prev = ptr ? (T*)ptr->prev : nullptr;
        }

        friend bool operator==(iterator const& lhs, iterator const& rhs)
        { return lhs.ptr == rhs.ptr; }
        
        friend bool operator!=(iterator const& lhs, iterator const& rhs)
        { return !(lhs.ptr == rhs.ptr); }
        
        iterator& operator++() { reset(next); return *this; }
        iterator operator++(int) { iterator ret = *this; ++(*this); return ret; }
        iterator& operator--() { reset(prev); return *this; }
        iterator operator--(int) { iterator ret = *this; --(*this); return ret; }
        T& operator*() { return *(T*)ptr; }
        T* operator->() { return (T*)ptr; }
};

TSQueue

libgo基于SList实现了一个线程安全的队列,用于协程的调度队列,与SList设计类似,在进行负载均衡调度时会大量操作这个TSQueue,所以还有必要分下这个线程安全队列的设计。
UML类图如下:
在这里插入图片描述
TSQueue为类模板,含有两个模板参数,T为队列元素类型,ThreadSafe可以选择是否为线程安全,这里使用到了一些元编程思想,根据ThreadSafe非类型模板参数(编译时已经确定),使用std::conditional进行选择,若线程安全则使用LFLock,每次操作时进行真正的加锁,若非线程安全则使用FakeLock,每次操作并没有加锁,这样的设计思想值得我们借鉴。

template <typename T, bool ThreadSafe = true>
class TSQueue
{
   static_assert(std::is_base_of<TSQueueHook, T>::value, "T must be base of TSQueueHook");

public:
   using lock_t    = typename std::conditional<ThreadSafe, LFLock, FakeLock>::type;
   using LockGuard = typename std::conditional<ThreadSafe, std::lock_guard<LFLock>, fake_lock_guard>::type;
};

Attribute

TSQueue成员变量:

  • ownerLock_ : 锁
  • lock_ : 锁指针,可以与其它TSQueue共用一把锁
  • head_ : 哨兵头节点,不存放数据
  • tail_ : 尾结点
  • count_ : 节点数量
  • check_ : 可选的检测指针
    //锁
    lock_t ownerLock_;
    //锁指针,可以与其它TSQueue共用一把锁
    lock_t *lock_;
    //哨兵头节点,不存放数据
    TSQueueHook* head_;
    //尾结点
    TSQueueHook* tail_;
    //节点数量
    volatile std::size_t count_;
    // 可选的检测指针
    void *check_; 

Method

void front(T *&out)

获取头结点,存放在out中

  ALWAYS_INLINE void front(T *&out)
 {
   //加锁
   LockGuard lock(*lock_);
   out = (T *)head_->next;
   //设置检测节点
   if (out)
    out->check_ = check_;
 }

void next(T *ptr, T *&out)

获取ptr的下一个节点存放在out中

 ALWAYS_INLINE void next(T *ptr, T *&out)
 {
   LockGuard lock(*lock_);
   nextWithoutLock(ptr, out);
 }

T *pop()

出队一个元素

ALWAYS_INLINE T *pop()
{
  if (head_ == tail_)
     return nullptr;
     
     LockGuard lock(*lock_);
  if (head_ == tail_)
     return nullptr;
     
     TSQueueHook *ptr = head_->next;
  //ptr为尾节点,将tail_设置为head_
  if (ptr == tail_)
     tail_ = head_;
     
     head_->next = ptr->next;
  //将ptr重置
  if (ptr->next)
      ptr->next->prev = head_;
      
      ptr->prev = ptr->next = nullptr;
      ptr->check_ = nullptr;
      --count_;
     //引用计数减一
     DecrementRef((T *)ptr);
     return (T *)ptr;
}

void push(SList && elements)

将一个SList加入队列中

ALWAYS_INLINE void push(SList<T> && elements)
{
        if (elements.empty()) return ;
        LockGuard lock(*lock_);
        pushWithoutLock(std::move(elements));
}

void pushWithoutLock(SList && elements)

将一个SList移动到队列中(无加锁)

ALWAYS_INLINE void pushWithoutLock(SList<T> && elements)
{
  if (elements.empty()) return ;
    assert(elements.head_->prev == nullptr);
    assert(elements.tail_->next == nullptr);
    TSQueueHook* listHead = elements.head_;
    count_ += elements.size();
    //链接到链表尾部
    tail_->link(listHead);
    
    tail_ = elements.tail_;
    //将elements设为空
    elements.stealed();
}

SList<T> pop_front(uint32_t n)

从队列中线程安全的pop出n个元素以SList返回,获取的节点数可能小于n

  ALWAYS_INLINE SList<T> pop_front(uint32_t n)
    {
        //队列为空,直接返回空链表
        if (head_ == tail_) return SList<T>();
        LockGuard lock(*lock_);
        if (head_ == tail_) return SList<T>();
        //获取第一个节点
        TSQueueHook* first = head_->next;
        TSQueueHook* last = first;
        uint32_t c = 1;
        //向后移动n次
        for (; c < n && last->next; ++c) {
            last = last->next;
        }
        //尾结点处理
        if (last == tail_) tail_ = head_;
        
        head_->next = last->next;
        //将后续节链接到头节点
        if (last->next) last->next->prev = head_;
        //断开链接
        first->prev = last->next = nullptr;
        
        count_ -= c;
        
        return SList<T>(first, last, c);
    }

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号