一个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_;
};