folly 开源库源码学习:无界队列 concurrency / UnboundedQueue.h

UnboundedQueue

  • 无界队列支持以下选择:
    • 单个/多个生产者
    • 单个/多个消费者
    • 阻塞/自旋等待
    • 无等待、定时
  • 生产者操作永远不会失败或等待(除非内存不够)
  • 适用场景:
    • 小型有界队列会在高并发下导致死锁或性能下降
    • 不需担心队列过度增长
  • 不适用场景
    • 有队列过度增长的风险,并且允许较大的边界,可以用 DynamicBoundedQueue
    • 元素入队列时不能分配空间,或必须有较小的边界,可以用fixed-size MPMCQueue 或者ProducerConsumerQueue

 

1、生产者与消费者

  struct Consumer {
    Atom<Segment*> head;
    Atom<Ticket> ticket;
    hazptr_obj_cohort<Atom> cohort;
    explicit Consumer(Segment* s) : head(s), ticket(0) {
      s->set_cohort_no_tag(&cohort); // defined in hazptr_obj
    }
  };
  struct Producer {
    Atom<Segment*> tail;
    Atom<Ticket> ticket;
    explicit Producer(Segment* s) : tail(s), ticket(0) {}
  };

// 内部定义了一个生产者一个消费者结构,分别管理队列尾部和头部
  alignas(Align) Consumer c_;
  alignas(Align) Producer p_;

 

2、元素入队列

  FOLLY_ALWAYS_INLINE void enqueue(const T& arg) {
    enqueueImpl(arg);
  }
……
  template <typename Arg>
  FOLLY_ALWAYS_INLINE void enqueueImpl(Arg&& arg) {
    if (SPSC) { // 单个生产者消费者
      Segment* s = tail();
      enqueueCommon(s, std::forward<Arg>(arg));
    } else {
      // Using hazptr_holder instead of hazptr_local because it is
      // possible that the T ctor happens to use hazard pointers.
      hazptr_holder<Atom> hptr;
      Segment* s = hptr.get_protected(p_.tail);
      enqueueCommon(s, std::forward<Arg>(arg));
    }
  }
……
  /** enqueueCommon */
  template <typename Arg>
  FOLLY_ALWAYS_INLINE void     enqueueCommon(Segment* s, Arg&& arg) {
    Ticket t = fetchIncrementProducerTicket(); // 增加生产者ticket+1
    if (!SingleProducer) {
      s = findSegment(s, t); // 这一步保证新的ticket有一个隶属的segment,每个segment大小segmentsize,t大于min+segmentsize,则会新增一个segment,min也随之增加。
    }
    DCHECK_GE(t, s->minTicket()); // t应该大于等于当前segment的 minticket
    DCHECK_LT(t, s->minTicket() + SegmentSize); // t应该小于当前segmment的末尾
    size_t idx = index(t); // 找到合适的segment下标
    Entry& e = s->entry(idx);  // 根据下标idx找到segment
    e.putItem(std::forward<Arg>(arg)); // 在当前节点entry的item_位置创建元素,调用信号量成员flag_ 的post函数
    if (responsibleForAlloc(t)) { // 当前ticket是segment的第一个元素,为了保证后面由空余segment,多分配一个segment
      allocNextSegment(s);
    }
    if (responsibleForAdvance(t)) { // 当前ticket是所在segment的末尾,生产者队列的tail 要指向下一个segment的开头
      advanceTail(s);
    }
  }

 

3、元素出队列

  FOLLY_ALWAYS_INLINE T dequeue() noexcept {
    return dequeueImpl();
  }
……
  FOLLY_ALWAYS_INLINE T dequeueImpl() noexcept {
    if (SPSC) {
      Segment* s = head();
      return dequeueCommon(s);
    } else {
      // Using hazptr_holder instead of hazptr_local because it is
      // possible to call the T dtor and it may happen to use hazard
      // pointers.
      hazptr_holder<Atom> hptr;
      Segment* s = hptr.get_protected(c_.head);
      return dequeueCommon(s);
    }
  }
……
  /** dequeueCommon */
  FOLLY_ALWAYS_INLINE T dequeueCommon(Segment* s) noexcept {
    Ticket t = fetchIncrementConsumerTicket(); // 消费者队列的ticket自增1
    if (!SingleConsumer) {
      s = findSegment(s, t); // 找到对应的segment
    }
    size_t idx = index(t);
    Entry& e = s->entry(idx);
    auto res = e.takeItem(); // 获取当前元素
    if (responsibleForAdvance(t)) { // 当前位置是segment的末尾,则将消费者的head指向下个segment的开头
      advanceHead(s);
    }
    return res;
  }

 

这里无界队列的实现有点类似STL 的 dequeue,生产证在队列尾部添加元素,消费者在队列头部消费元素,整个队列由多个segment构成,利用了数组和链表的优势。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值