folly 开源库源码学习:动态有界队列 concurrency / DynamicBoundedQueue.h

DynamicBoundedQueue是一种可动态调整容量的队列,用于平衡生产者与消费者间的数据流,防止死锁,支持元素的不同权重,适用于小容量队列可能导致性能下降的场景。通过调整队列元素的权重和队列容量,实现内存的有效管理和使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DynamicBoundedQueue

  • 通过队列元素的增减,管理内存的扩张与缩减
  • 容量可调整,作为队列平衡了生产者消费者的不平衡
  • 还可防止死锁,将容量增加到生产者不需等待的大小
  • 队列元素有权重,默认为1;最大权重是队列容量;权重不影响顺序,还是FIFO
  • 适用场景:当小容量队列会导致思索或者性能下降时;典型队列大小比一般队列小得多;需要支持元素不同权重。
  • 不适用场景:不支持动态内存分配时;最大容量较小,可用定长的MPMCQueue;若没有增长过大的风险,可以用UnboundedQueue

 

1、基本定义

template <
    typename T,
    bool SingleProducer, // 生产者
    bool SingleConsumer, // 消费者
    bool MayBlock,
    size_t LgSegmentSize = 8,
    size_t LgAlign = 7,
    typename WeightFn = DefaultWeightFn<T>,// 权重函数
    template <typename> class Atom = std::atomic>
class DynamicBoundedQueue {
  using Weight = uint64_t;

  enum WaitingState : uint32_t {
    NOTWAITING = 0,
    WAITING = 1,
  };

 static constexpr bool SPSC = SingleProducer && SingleConsumer;
  static constexpr size_t Align = 1u << LgAlign;

  static_assert(LgAlign < 16, "LgAlign must be < 16");

  /// 数据成员

  // Read mostly by producers
  alignas(Align) Atom<Weight> debit_; // 只有生产者频繁写这个变量
  Atom<Weight> capacity_; // 容量重置时写(极少)

  // Read mostly by consumers
  alignas(Align) Atom<Weight> credit_; // 只有消费者会频繁写这个变量
  Atom<Weight> threshold_; // 容量重置时会写(极少)

  // 消费者和生产者会读写(极少)
  // 容量满时消费者可能会频繁读取
  alignas(Align) Atom<Weight> transfer_;
  detail::Futex<Atom> waiting_;

  // Underlying unbounded queue
  UnboundedQueue<
      T,
      SingleProducer,
      SingleConsumer,
      MayBlock,
      LgSegmentSize,
      LgAlign,
      Atom>
      q_;

 

2、元素入队列

  FOLLY_ALWAYS_INLINE void enqueue(const T& v) {
    enqueueImpl(v);
  }
……
  FOLLY_ALWAYS_INLINE void enqueueImpl(Arg&& v) {
    tryEnqueueUntilImpl(
        std::forward<Arg>(v), std::chrono::steady_clock::time_point::max());
  }
……
  template <typename Clock, typename Duration, typename Arg>
  FOLLY_ALWAYS_INLINE bool tryEnqueueUntilImpl(
      Arg&& v,
      const std::chrono::time_point<Clock, Duration>& deadline) {
    Weight weight = WeightFn()(std::forward<Arg>(v)); // 计算权重
    if (LIKELY(tryAddDebit(weight))) { // 尝试加上新元素的权重
      q_.enqueue(std::forward<Arg>(v)); // 加成功后元素入队列,调用UnboundedQueue类型成员的enqueue函数
      return true;
    }
    return tryEnqueueUntilSlow(std::forward<Arg>(v), deadline); // 加权重失败,再试一次
  }
  FOLLY_ALWAYS_INLINE bool tryAddDebit(Weight weight) noexcept {
    Weight capacity = getCapacity();
    Weight before = fetchAddDebit(weight); // 原子的增加权重,根据是否单个消费者分别处理
    if (LIKELY(before + weight <= capacity)) { // 权重总和最大不超过容量
      return true;
    } else {
      subDebit(weight); // 否则在总权重中减去此次权重,前提是足够减
      return false;
    }
  }

元素入队列失败(一般是权重超过总和了)再次尝试:

  template <typename Clock, typename Duration, typename Arg>
  bool tryEnqueueUntilSlow(
      Arg&& v,
      const std::chrono::time_point<Clock, Duration>& deadline) {
    Weight weight = WeightFn()(std::forward<Arg>(v));
    if (canEnqueue(deadline, weight)) { // 判断是否可以加入队列,可以进入等待
      q_.enqueue(std::forward<Arg>(v));
      return true;
    } else {
      return false;
    }
  }
……
  template <typename Clock, typename Duration>
  bool canEnqueue(
      const std::chrono::time_point<Clock, Duration>& deadline,
      Weight weight) noexcept {
    Weight capacity = getCapacity();
    while (true) { // 放在while 循环内一直尝试加权
      tryReduceDebit(); // 尝试减权重,是尝试将总权重减去一个transfer的值??
      Weight debit = getDebit();
      if ((debit + weight <= capacity) && tryAddDebit(weight)) { // 加权成功直接返回
        return true;
      }
      if (deadline < Clock::time_point::max() && Clock::now() >= deadline) {
        return false; // 超时了
      }
      if (MayBlock) { // 可以加锁
        if (canBlock(weight, capacity)) { // 剩余权重不够,先进入等待,等消费者唤醒。
          detail::futexWaitUntil(&waiting_, WAITING, deadline);
        }
      } else {
        asm_volatile_pause();
      }
    }
  }

插入队列:

调用的是UnboundedQueue 成员的enqueue函数

 

3、元素出队列

  FOLLY_ALWAYS_INLINE void dequeue(T& elem) {
    q_.dequeue(elem); // 调用成员无界队列的dequeue函数
    addCredit(WeightFn()(elem));
  }
  FOLLY_ALWAYS_INLINE void addCredit(Weight weight) noexcept {
    Weight before = fetchAddCredit(weight);
    Weight thresh = getThreshold();
    if (before + weight >= thresh && before < thresh) {
      transferCredit();
    }
  }
  void transferCredit() noexcept {
    Weight credit = takeCredit();
    transfer_.fetch_add(credit, std::memory_order_acq_rel);
    if (MayBlock) {
      std::atomic_thread_fence(std::memory_order_seq_cst);
      waiting_.store(NOTWAITING, std::memory_order_relaxed);
      detail::futexWake(&waiting_); // 唤醒,例如阻塞在入队列的生产者
    }
  }

 

这里的流程是,消费者消费了记录释放了空间,增加credit,意思是信用,而生产者要入队列,消耗了空间,增加的是debit,意思是借款,然后又转移到transfer里,消费者如果发现credit超出限额可以从transfer中取一些。

这个动态队列因为内部是一个无界队列,不是固定的空间,所以比定长队列可以节省空间。“动态”在于可以用reset_capacity 方法重新设置容量,实现扩容或缩容。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值