BatchQueue数据结构

 一个BatchQueue的实现:

注意一下几点:

1. Reader会使用pop/front函数,用到了begin_chunk, begin_pos, spare_chunk;

  Writer使用push/back函数。用到了end_chunk,end_pos, back_chunk, back_pos, spare_chunk;

  Reader和Writer唯一共用的变量是spare_chunk, 所以需要对spare_chunk使用原子操作;

 spare_chunk是用来暂不销毁Reader pop数据后没用的chunk,这样Writer下次push数据需要新chunk的时候可以直接拿来用,不需要重新malloc了;

2. back_chunk,back_pos指向的是下一个可以写入数据的位置;

   end_check, end_pos指向的是back_pos的下一个位置。

3. 注意这里的Push函数并没有真的把数据放入,而只是改变了back_chunk, back_pos, end_chunk, end_pos的值, 真正的放入数据的操作在BatchQueue的使用者中进行;使用者先对back_chunk[back_pos]赋值,让后调用Push函数;

#include <boost/atomic.hpp>

#define NO_MEM       (-2)                // Memory allocation failed

//  This class is an efficient queue implementation. The main goal is
//  to minimize number of allocations/deallocations needed. Thus it
//  allocates/deallocates elements in batches of N.
//
//  This class allows one thread to use push/back function and another one
//  to use pop/front functions. However, user must ensure that there's no
//  pop on the empty queue and that both threads don't access the same
//  element in unsynchronized manner.
//
//  T is the type of the object in the queue.
//  N is granularity of the queue
template <typename T, int N>
class BatchQueue {
 public:
  // Create the queue.
  inline BatchQueue ()
    : begin_chunk_(NULL)
    , end_chunk_(NULL)
    , back_chunk_(NULL)
    , begin_pos_(0)
    , back_pos_(0)
    , end_pos_(0)
    , spare_chunk_(NULL) {}

  // Destroy the queue.
  inline ~BatchQueue () {
    while (true) {
      if (begin_chunk_ == end_chunk_) {
        free(begin_chunk_);
        break;
      }
      Chunk *o = begin_chunk_;
      begin_chunk_ = begin_chunk_->next;
      free (o);
    }

    Chunk *sc = spare_chunk_.exchange(NULL, boost::memory_order_relaxed);
    free (sc);
  }

  /// Initialize the queue. May fail when memory allocation failed.
  inline int Init() {
    begin_chunk_ = (Chunk*) malloc (sizeof (Chunk));
    if (NULL == begin_chunk_) return NO_MEM;
    begin_pos_ = 0;
    back_chunk_ = NULL;
    back_pos_ = 0;
    end_chunk_ = begin_chunk_;
    end_pos_ = 0;
    return 0;
  }

  // Returns reference to the first element of the queue.
  // If the queue is empty, behavior is undefined.
  inline T& Front() {
    return begin_chunk_->values[begin_pos_];
  }

  //  Returns reference to the last element of the queue.
  //  If the queue is empty, behavior is undefined.
  inline T& Back() {
    return back_chunk_->values[back_pos_];
  }

  /// Adds an element to the back end of the queue.
  /// May fail when memory allocation failed.
  inline int Push() {
    back_chunk_ = end_chunk_;
    back_pos_ = end_pos_;

    if (++end_pos_ != N)
      return 0;

    Chunk* sc = spare_chunk_.exchange(NULL, boost::memory_order_relaxed);
    if (sc) {
      end_chunk_->next = sc;
      sc->prev = end_chunk_;
    } else {
      Chunk* new_chunk = (Chunk*)malloc(sizeof(Chunk));
      if (NULL == new_chunk) {
        --end_pos_;
        return NO_MEM;
      }
      end_chunk_->next = new_chunk;
      end_chunk_->next->prev = end_chunk_;
    }
    end_chunk_ = end_chunk_->next;
    end_pos_ = 0;
    return 0;
  }

  //  Removes element from the back end of the queue. In other words
  //  it rollbacks last push to the queue. Take care: Caller is
  //  responsible for destroying the object being unpushed.
  //  The caller must also guarantee that the queue isn't empty when
  //  unpush is called. It cannot be done automatically as the read
  //  side of the queue can be managed by different, completely
  //  unsynchronized thread.
  inline void Unpush() {
    //  First, move 'back' one position backwards.
    if (back_pos_) {
      --back_pos_;
    } else {
      back_pos_ = N - 1;
      back_chunk_ = back_chunk_->prev;
    }

    //  Now, move 'end' position backwards. Note that obsolete end chunk
    //  is not used as a spare chunk. The analysis shows that doing so
    //  would require free and atomic operation per chunk deallocated
    //  instead of a simple free.
    if (end_pos_) {
      --end_pos_;
    } else {
      end_pos_ = N - 1;
      end_chunk_ = end_chunk_->prev;
      free (end_chunk_->next);
      end_chunk_->next = NULL;
    }
  }

  //  Removes an element from the front end of the queue.
  inline void Pop () {
    if (++ begin_pos_ == N) {
      Chunk* o = begin_chunk_;
      begin_chunk_ = begin_chunk_->next;
      begin_chunk_->prev = NULL;
      begin_pos_ = 0;

      //  'o' has been more recently used than spare_chunk,
      //  so for cache reasons we'll get rid of the spare and
      //  use 'o' as the spare.
      Chunk* cs = spare_chunk_.exchange(o, boost::memory_order_relaxed);
      free(cs);
    }
  }

 private:

  //  Individual memory chunk to hold N elements.
  struct Chunk {
    T values [N];
    Chunk* prev;
    Chunk* next;
  };
  //  Back position may point to invalid memory if the queue is empty,
  //  while begin & end positions are always valid. Begin position is
  //  accessed exclusively be queue reader (front/pop), while back and
  //  end positions are accessed exclusively by queue writer (back/push).
  Chunk* begin_chunk_;
  int begin_pos_;
  Chunk* back_chunk_;
  int back_pos_;
  Chunk* end_chunk_;
  int end_pos_;

  //  People are likely to produce and consume at similar rates.  In
  //  this scenario holding onto the most recently freed chunk saves
  //  us from having to call malloc/free.
  boost::atomic<Chunk*> spare_chunk_;
};

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值