JODA  0.13.1 (59b41972)
JSON On-Demand Analysis
readerwriterqueue.h
Go to the documentation of this file.
1 // ©2013-2016 Cameron Desrochers.
2 // Distributed under the simplified BSD license (see the license file that
3 // should have come with this header).
4 
5 #pragma once
6 
7 #include <cassert>
8 #include <cstdint>
9 #include <cstdlib> // For malloc/free/abort & size_t
10 #include <new>
11 #include <stdexcept>
12 #include <type_traits>
13 #include <utility>
14 #include "atomicops.h"
15 #if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012
16 #include <chrono>
17 #endif
18 
19 // A lock-free queue for a single-consumer, single-producer architecture.
20 // The queue is also wait-free in the common path (except if more memory
21 // needs to be allocated, in which case malloc is called).
22 // Allocates memory sparingly (O(lg(n) times, amortized), and only once if
23 // the original maximum size estimate is never exceeded.
24 // Tested on x86/x64 processors, but semantics should be correct for all
25 // architectures (given the right implementations in atomicops.h), provided
26 // that aligned integer and pointer accesses are naturally atomic.
27 // Note that there should only be one consumer thread and producer thread;
28 // Switching roles of the threads, or using multiple consecutive threads for
29 // one role, is not safe unless properly synchronized.
30 // Using the queue exclusively from one thread is fine, though a bit silly.
31 
32 #ifndef MOODYCAMEL_CACHE_LINE_SIZE
33 #define MOODYCAMEL_CACHE_LINE_SIZE 64
34 #endif
35 
36 #ifndef MOODYCAMEL_EXCEPTIONS_ENABLED
37 #if (defined(_MSC_VER) && defined(_CPPUNWIND)) || \
38  (defined(__GNUC__) && defined(__EXCEPTIONS)) || \
39  (!defined(_MSC_VER) && !defined(__GNUC__))
40 #define MOODYCAMEL_EXCEPTIONS_ENABLED
41 #endif
42 #endif
43 
44 #ifdef AE_VCPP
45 #pragma warning(push)
46 #pragma warning( \
47  disable : 4324) // structure was padded due to __declspec(align())
48 #pragma warning(disable : 4820) // padding was added
49 #pragma warning(disable : 4127) // conditional expression is constant
50 #endif
51 
52 namespace moodycamel {
53 
54 template <typename T, size_t MAX_BLOCK_SIZE = 512>
56  // Design: Based on a queue-of-queues. The low-level queues are just
57  // circular buffers with front and tail indices indicating where the
58  // next element to dequeue is and where the next element can be enqueued,
59  // respectively. Each low-level queue is called a "block". Each block
60  // wastes exactly one element's worth of space to keep the design simple
61  // (if front == tail then the queue is empty, and can't be full).
62  // The high-level queue is a circular linked list of blocks; again there
63  // is a front and tail, but this time they are pointers to the blocks.
64  // The front block is where the next element to be dequeued is, provided
65  // the block is not empty. The back block is where elements are to be
66  // enqueued, provided the block is not full.
67  // The producer thread owns all the tail indices/pointers. The consumer
68  // thread owns all the front indices/pointers. Both threads read each
69  // other's variables, but only the owning thread updates them. E.g. After
70  // the consumer reads the producer's tail, the tail may change before the
71  // consumer is done dequeuing an object, but the consumer knows the tail
72  // will never go backwards, only forwards.
73  // If there is no room to enqueue an object, an additional block (of
74  // equal size to the last block) is added. Blocks are never removed.
75 
76  public:
77  // Constructs a queue that can hold maxSize elements without further
78  // allocations. If more than MAX_BLOCK_SIZE elements are requested,
79  // then several blocks of MAX_BLOCK_SIZE each are reserved (including
80  // at least one extra buffer block).
81  explicit ReaderWriterQueue(size_t maxSize = 15)
82 #ifndef NDEBUG
83  : enqueuing(false),
84  dequeuing(false)
85 #endif
86  {
87  assert(maxSize > 0);
88  assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) &&
89  "MAX_BLOCK_SIZE must be a power of 2");
90  assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2");
91 
92  Block* firstBlock = nullptr;
93 
94  largestBlockSize = ceilToPow2(
95  maxSize +
96  1); // We need a spare slot to fit maxSize elements in the block
97  if (largestBlockSize > MAX_BLOCK_SIZE * 2) {
98  // We need a spare block in case the producer is writing to a different
99  // block the consumer is reading from, and wants to enqueue the maximum
100  // number of elements. We also need a spare element in each block to avoid
101  // the ambiguity between front == tail meaning "empty" and "full". So the
102  // effective number of slots that are guaranteed to be usable at any time
103  // is the block size - 1 times the number of blocks - 1. Solving for
104  // maxSize and applying a ceiling to the division gives us (after
105  // simplifying):
106  size_t initialBlockCount =
107  (maxSize + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1);
108  largestBlockSize = MAX_BLOCK_SIZE;
109  Block* lastBlock = nullptr;
110  for (size_t i = 0; i != initialBlockCount; ++i) {
111  auto block = make_block(largestBlockSize);
112  if (block == nullptr) {
113 #ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
114  throw std::bad_alloc();
115 #else
116  abort();
117 #endif
118  }
119  if (firstBlock == nullptr) {
120  firstBlock = block;
121  } else {
122  lastBlock->next = block;
123  }
124  lastBlock = block;
125  block->next = firstBlock;
126  }
127  } else {
128  firstBlock = make_block(largestBlockSize);
129  if (firstBlock == nullptr) {
130 #ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
131  throw std::bad_alloc();
132 #else
133  abort();
134 #endif
135  }
136  firstBlock->next = firstBlock;
137  }
138  frontBlock = firstBlock;
139  tailBlock = firstBlock;
140 
141  // Make sure the reader/writer threads will have the initialized memory
142  // setup above:
144  }
145 
146  // Note: The queue should not be accessed concurrently while it's
147  // being deleted. It's up to the user to synchronize this.
149  // Make sure we get the latest version of all variables from other CPUs:
151 
152  // Destroy any remaining objects in queue and free memory
153  Block* frontBlock_ = frontBlock;
154  Block* block = frontBlock_;
155  do {
156  Block* nextBlock = block->next;
157  size_t blockFront = block->front;
158  size_t blockTail = block->tail;
159 
160  for (size_t i = blockFront; i != blockTail;
161  i = (i + 1) & block->sizeMask) {
162  auto element = reinterpret_cast<T*>(block->data + i * sizeof(T));
163  element->~T();
164  (void)element;
165  }
166 
167  auto rawBlock = block->rawThis;
168  block->~Block();
169  std::free(rawBlock);
170  block = nextBlock;
171  } while (block != frontBlock_);
172  }
173 
174  // Enqueues a copy of element if there is room in the queue.
175  // Returns true if the element was enqueued, false otherwise.
176  // Does not allocate memory.
177  AE_FORCEINLINE bool try_enqueue(T const& element) {
178  return inner_enqueue<CannotAlloc>(element);
179  }
180 
181  // Enqueues a moved copy of element if there is room in the queue.
182  // Returns true if the element was enqueued, false otherwise.
183  // Does not allocate memory.
184  AE_FORCEINLINE bool try_enqueue(T&& element) {
185  return inner_enqueue<CannotAlloc>(std::forward<T>(element));
186  }
187 
188  // Enqueues a copy of element on the queue.
189  // Allocates an additional block of memory if needed.
190  // Only fails (returns false) if memory allocation fails.
191  AE_FORCEINLINE bool enqueue(T const& element) {
192  return inner_enqueue<CanAlloc>(element);
193  }
194 
195  // Enqueues a moved copy of element on the queue.
196  // Allocates an additional block of memory if needed.
197  // Only fails (returns false) if memory allocation fails.
198  AE_FORCEINLINE bool enqueue(T&& element) {
199  return inner_enqueue<CanAlloc>(std::forward<T>(element));
200  }
201 
202  // Attempts to dequeue an element; if the queue is empty,
203  // returns false instead. If the queue has at least one element,
204  // moves front to result using operator=, then returns true.
205  template <typename U>
206  bool try_dequeue(U& result) {
207 #ifndef NDEBUG
208  ReentrantGuard guard(this->dequeuing);
209 #endif
210 
211  // High-level pseudocode:
212  // Remember where the tail block is
213  // If the front block has an element in it, dequeue it
214  // Else
215  // If front block was the tail block when we entered the function,
216  // return false Else advance to next block and dequeue the item there
217 
218  // Note that we have to use the value of the tail block from before we check
219  // if the front block is full or not, in case the front block is empty and
220  // then, before we check if the tail block is at the front block or not, the
221  // producer fills up the front block *and moves on*, which would make us
222  // skip a filled block. Seems unlikely, but was consistently reproducible in
223  // practice. In order to avoid overhead in the common case, though, we do a
224  // double-checked pattern where we have the fast path if the front block is
225  // not empty, then read the tail block, then re-read the front block and
226  // check if it's not empty again, then check if the tail block has advanced.
227 
228  Block* frontBlock_ = frontBlock.load();
229  size_t blockTail = frontBlock_->localTail;
230  size_t blockFront = frontBlock_->front.load();
231 
232  if (blockFront != blockTail ||
233  blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
235 
236  non_empty_front_block:
237  // Front block not empty, dequeue from here
238  auto element =
239  reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
240  result = std::move(*element);
241  element->~T();
242 
243  blockFront = (blockFront + 1) & frontBlock_->sizeMask;
244 
246  frontBlock_->front = blockFront;
247  } else if (frontBlock_ != tailBlock.load()) {
249 
250  frontBlock_ = frontBlock.load();
251  blockTail = frontBlock_->localTail = frontBlock_->tail.load();
252  blockFront = frontBlock_->front.load();
254 
255  if (blockFront != blockTail) {
256  // Oh look, the front block isn't empty after all
257  goto non_empty_front_block;
258  }
259 
260  // Front block is empty but there's another block ahead, advance to it
261  Block* nextBlock = frontBlock_->next;
262  // Don't need an acquire fence here since next can only ever be set on the
263  // tailBlock, and we're not the tailBlock, and we did an acquire earlier
264  // after reading tailBlock which ensures next is up-to-date on this CPU in
265  // case we recently were at tailBlock.
266 
267  size_t nextBlockFront = nextBlock->front.load();
268  size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
270 
271  // Since the tailBlock is only ever advanced after being written to,
272  // we know there's for sure an element to dequeue on it
273  assert(nextBlockFront != nextBlockTail);
274  AE_UNUSED(nextBlockTail);
275 
276  // We're done with this block, let the producer use it if it needs
277  fence(memory_order_release); // Expose possibly pending changes to
278  // frontBlock->front from last dequeue
279  frontBlock = frontBlock_ = nextBlock;
280 
281  compiler_fence(memory_order_release); // Not strictly needed
282 
283  auto element =
284  reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
285 
286  result = std::move(*element);
287  element->~T();
288 
289  nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
290 
292  frontBlock_->front = nextBlockFront;
293  } else {
294  // No elements in current block and no other block to advance to
295  return false;
296  }
297 
298  return true;
299  }
300 
301  // Returns a pointer to the front element in the queue (the one that
302  // would be removed next by a call to `try_dequeue` or `pop`). If the
303  // queue appears empty at the time the method is called, nullptr is
304  // returned instead.
305  // Must be called only from the consumer thread.
306  T* peek() {
307 #ifndef NDEBUG
308  ReentrantGuard guard(this->dequeuing);
309 #endif
310  // See try_dequeue() for reasoning
311 
312  Block* frontBlock_ = frontBlock.load();
313  size_t blockTail = frontBlock_->localTail;
314  size_t blockFront = frontBlock_->front.load();
315 
316  if (blockFront != blockTail ||
317  blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
319  non_empty_front_block:
320  return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
321  } else if (frontBlock_ != tailBlock.load()) {
323  frontBlock_ = frontBlock.load();
324  blockTail = frontBlock_->localTail = frontBlock_->tail.load();
325  blockFront = frontBlock_->front.load();
327 
328  if (blockFront != blockTail) {
329  goto non_empty_front_block;
330  }
331 
332  Block* nextBlock = frontBlock_->next;
333 
334  size_t nextBlockFront = nextBlock->front.load();
336 
337  assert(nextBlockFront != nextBlock->tail.load());
338  return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T));
339  }
340 
341  return nullptr;
342  }
343 
344  // Removes the front element from the queue, if any, without returning it.
345  // Returns true on success, or false if the queue appeared empty at the time
346  // `pop` was called.
347  bool pop() {
348 #ifndef NDEBUG
349  ReentrantGuard guard(this->dequeuing);
350 #endif
351  // See try_dequeue() for reasoning
352 
353  Block* frontBlock_ = frontBlock.load();
354  size_t blockTail = frontBlock_->localTail;
355  size_t blockFront = frontBlock_->front.load();
356 
357  if (blockFront != blockTail ||
358  blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
360 
361  non_empty_front_block:
362  auto element =
363  reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
364  element->~T();
365 
366  blockFront = (blockFront + 1) & frontBlock_->sizeMask;
367 
369  frontBlock_->front = blockFront;
370  } else if (frontBlock_ != tailBlock.load()) {
372  frontBlock_ = frontBlock.load();
373  blockTail = frontBlock_->localTail = frontBlock_->tail.load();
374  blockFront = frontBlock_->front.load();
376 
377  if (blockFront != blockTail) {
378  goto non_empty_front_block;
379  }
380 
381  // Front block is empty but there's another block ahead, advance to it
382  Block* nextBlock = frontBlock_->next;
383 
384  size_t nextBlockFront = nextBlock->front.load();
385  size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
387 
388  assert(nextBlockFront != nextBlockTail);
389  AE_UNUSED(nextBlockTail);
390 
392  frontBlock = frontBlock_ = nextBlock;
393 
395 
396  auto element =
397  reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
398  element->~T();
399 
400  nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
401 
403  frontBlock_->front = nextBlockFront;
404  } else {
405  // No elements in current block and no other block to advance to
406  return false;
407  }
408 
409  return true;
410  }
411 
412  // Returns the approximate number of items currently in the queue.
413  // Safe to call from both the producer and consumer threads.
414  inline size_t size_approx() const {
415  size_t result = 0;
416  Block* frontBlock_ = frontBlock.load();
417  Block* block = frontBlock_;
418  do {
420  size_t blockFront = block->front.load();
421  size_t blockTail = block->tail.load();
422  result += (blockTail - blockFront) & block->sizeMask;
423  block = block->next.load();
424  } while (block != frontBlock_);
425  return result;
426  }
427 
428  private:
429  enum AllocationMode { CanAlloc, CannotAlloc };
430 
431  template <AllocationMode canAlloc, typename U>
432  bool inner_enqueue(U&& element) {
433 #ifndef NDEBUG
434  ReentrantGuard guard(this->enqueuing);
435 #endif
436 
437  // High-level pseudocode (assuming we're allowed to alloc a new block):
438  // If room in tail block, add to tail
439  // Else check next block
440  // If next block is not the head block, enqueue on next block
441  // Else create a new block and enqueue there
442  // Advance tail to the block we just enqueued to
443 
444  Block* tailBlock_ = tailBlock.load();
445  size_t blockFront = tailBlock_->localFront;
446  size_t blockTail = tailBlock_->tail.load();
447 
448  size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask;
449  if (nextBlockTail != blockFront ||
450  nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) {
452  // This block has room for at least one more element
453  char* location = tailBlock_->data + blockTail * sizeof(T);
454  new (location) T(std::forward<U>(element));
455 
457  tailBlock_->tail = nextBlockTail;
458  } else {
460  if (tailBlock_->next.load() != frontBlock) {
461  // Note that the reason we can't advance to the frontBlock and start
462  // adding new entries there is because if we did, then dequeue would
463  // stay in that block, eventually reading the new values, instead of
464  // advancing to the next full block (whose values were enqueued first
465  // and so should be consumed first).
466 
467  fence(memory_order_acquire); // Ensure we get latest writes if we got
468  // the latest frontBlock
469 
470  // tailBlock is full, but there's a free block ahead, use it
471  Block* tailBlockNext = tailBlock_->next.load();
472  size_t nextBlockFront = tailBlockNext->localFront =
473  tailBlockNext->front.load();
474  nextBlockTail = tailBlockNext->tail.load();
476 
477  // This block must be empty since it's not the head block and we
478  // go through the blocks in a circle
479  assert(nextBlockFront == nextBlockTail);
480  tailBlockNext->localFront = nextBlockFront;
481 
482  char* location = tailBlockNext->data + nextBlockTail * sizeof(T);
483  new (location) T(std::forward<U>(element));
484 
485  tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask;
486 
488  tailBlock = tailBlockNext;
489  } else if (canAlloc == CanAlloc) {
490  // tailBlock is full and there's no free block ahead; create a new block
491  auto newBlockSize = largestBlockSize >= MAX_BLOCK_SIZE
492  ? largestBlockSize
493  : largestBlockSize * 2;
494  auto newBlock = make_block(newBlockSize);
495  if (newBlock == nullptr) {
496  // Could not allocate a block!
497  return false;
498  }
499  largestBlockSize = newBlockSize;
500 
501  new (newBlock->data) T(std::forward<U>(element));
502 
503  assert(newBlock->front == 0);
504  newBlock->tail = newBlock->localTail = 1;
505 
506  newBlock->next = tailBlock_->next.load();
507  tailBlock_->next = newBlock;
508 
509  // Might be possible for the dequeue thread to see the new
510  // tailBlock->next *without* seeing the new tailBlock value, but this is
511  // OK since it can't advance to the next block until tailBlock is set
512  // anyway (because the only case where it could try to read the next is
513  // if it's already at the tailBlock, and it won't advance past tailBlock
514  // in any circumstance).
515 
517  tailBlock = newBlock;
518  } else if (canAlloc == CannotAlloc) {
519  // Would have had to allocate a new block to enqueue, but not allowed
520  return false;
521  } else {
522  assert(false && "Should be unreachable code");
523  return false;
524  }
525  }
526 
527  return true;
528  }
529 
530  // Disable copying
532 
533  // Disable assignment
534  ReaderWriterQueue& operator=(ReaderWriterQueue const&) {}
535 
536  AE_FORCEINLINE static size_t ceilToPow2(size_t x) {
537  // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
538  --x;
539  x |= x >> 1;
540  x |= x >> 2;
541  x |= x >> 4;
542  for (size_t i = 1; i < sizeof(size_t); i <<= 1) {
543  x |= x >> (i << 3);
544  }
545  ++x;
546  return x;
547  }
548 
549  template <typename U>
550  static AE_FORCEINLINE char* align_for(char* ptr) {
551  const std::size_t alignment = std::alignment_of<U>::value;
552  return ptr +
553  (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) %
554  alignment;
555  }
556 
557  private:
558 #ifndef NDEBUG
559  struct ReentrantGuard {
560  ReentrantGuard(bool& _inSection) : inSection(_inSection) {
561  assert(!inSection &&
562  "ReaderWriterQueue does not support enqueuing or dequeuing "
563  "elements from other elements' ctors and dtors");
564  inSection = true;
565  }
566 
567  ~ReentrantGuard() { inSection = false; }
568 
569  private:
570  ReentrantGuard& operator=(ReentrantGuard const&);
571 
572  private:
573  bool& inSection;
574  };
575 #endif
576 
577  struct Block {
578  // Avoid false-sharing by putting highly contended variables on their own
579  // cache lines
580  weak_atomic<size_t> front; // (Atomic) Elements are read from here
581  size_t
582  localTail; // An uncontended shadow copy of tail, owned by the consumer
583 
584  char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE -
585  sizeof(weak_atomic<size_t>) - sizeof(size_t)];
586  weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here
587  size_t localFront;
588 
589  char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE -
590  sizeof(weak_atomic<size_t>) -
591  sizeof(size_t)]; // next isn't very contended, but we
592  // don't want it on the same cache
593  // line as tail (which is)
594  weak_atomic<Block*> next; // (Atomic)
595 
596  char* data; // Contents (on heap) are aligned to T's alignment
597 
598  const size_t sizeMask;
599 
600  // size must be a power of two (and greater than 0)
601  Block(size_t const& _size, char* _rawThis, char* _data)
602  : front(0),
603  localTail(0),
604  tail(0),
605  localFront(0),
606  next(nullptr),
607  data(_data),
608  sizeMask(_size - 1),
609  rawThis(_rawThis) {}
610 
611  private:
612  // C4512 - Assignment operator could not be generated
613  Block& operator=(Block const&);
614 
615  public:
616  char* rawThis;
617  };
618 
619  static Block* make_block(size_t capacity) {
620  // Allocate enough memory for the block itself, as well as all the elements
621  // it will contain
622  auto size = sizeof(Block) + std::alignment_of<Block>::value - 1;
623  size += sizeof(T) * capacity + std::alignment_of<T>::value - 1;
624  auto newBlockRaw = static_cast<char*>(std::malloc(size));
625  if (newBlockRaw == nullptr) {
626  return nullptr;
627  }
628 
629  auto newBlockAligned = align_for<Block>(newBlockRaw);
630  auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block));
631  return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData);
632  }
633 
634  private:
635  weak_atomic<Block*>
636  frontBlock; // (Atomic) Elements are enqueued to this block
637 
638  char
639  cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)];
640  weak_atomic<Block*>
641  tailBlock; // (Atomic) Elements are dequeued from this block
642 
643  size_t largestBlockSize;
644 
645 #ifndef NDEBUG
646  bool enqueuing;
647  bool dequeuing;
648 #endif
649 };
650 
651 // Like ReaderWriterQueue, but also providees blocking operations
652 template <typename T, size_t MAX_BLOCK_SIZE = 512>
654  private:
655  typedef ::moodycamel::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue;
656 
657  public:
658  explicit BlockingReaderWriterQueue(size_t maxSize = 15) : inner(maxSize) {}
659 
660  // Enqueues a copy of element if there is room in the queue.
661  // Returns true if the element was enqueued, false otherwise.
662  // Does not allocate memory.
663  AE_FORCEINLINE bool try_enqueue(T const& element) {
664  if (inner.try_enqueue(element)) {
665  sema.signal();
666  return true;
667  }
668  return false;
669  }
670 
671  // Enqueues a moved copy of element if there is room in the queue.
672  // Returns true if the element was enqueued, false otherwise.
673  // Does not allocate memory.
674  AE_FORCEINLINE bool try_enqueue(T&& element) {
675  if (inner.try_enqueue(std::forward<T>(element))) {
676  sema.signal();
677  return true;
678  }
679  return false;
680  }
681 
682  // Enqueues a copy of element on the queue.
683  // Allocates an additional block of memory if needed.
684  // Only fails (returns false) if memory allocation fails.
685  AE_FORCEINLINE bool enqueue(T const& element) {
686  if (inner.enqueue(element)) {
687  sema.signal();
688  return true;
689  }
690  return false;
691  }
692 
693  // Enqueues a moved copy of element on the queue.
694  // Allocates an additional block of memory if needed.
695  // Only fails (returns false) if memory allocation fails.
696  AE_FORCEINLINE bool enqueue(T&& element) {
697  if (inner.enqueue(std::forward<T>(element))) {
698  sema.signal();
699  return true;
700  }
701  return false;
702  }
703 
704  // Attempts to dequeue an element; if the queue is empty,
705  // returns false instead. If the queue has at least one element,
706  // moves front to result using operator=, then returns true.
707  template <typename U>
708  bool try_dequeue(U& result) {
709  if (sema.tryWait()) {
710  bool success = inner.try_dequeue(result);
711  assert(success);
712  AE_UNUSED(success);
713  return true;
714  }
715  return false;
716  }
717 
718  // Attempts to dequeue an element; if the queue is empty,
719  // waits until an element is available, then dequeues it.
720  template <typename U>
721  void wait_dequeue(U& result) {
722  sema.wait();
723  bool success = inner.try_dequeue(result);
724  AE_UNUSED(result);
725  assert(success);
726  AE_UNUSED(success);
727  }
728 
729  // Attempts to dequeue an element; if the queue is empty,
730  // waits until an element is available up to the specified timeout,
731  // then dequeues it and returns true, or returns false if the timeout
732  // expires before an element can be dequeued.
733  // Using a negative timeout indicates an indefinite timeout,
734  // and is thus functionally equivalent to calling wait_dequeue.
735  template <typename U>
736  bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) {
737  if (!sema.wait(timeout_usecs)) {
738  return false;
739  }
740  bool success = inner.try_dequeue(result);
741  AE_UNUSED(result);
742  assert(success);
743  AE_UNUSED(success);
744  return true;
745  }
746 
747 #if __cplusplus > 199711L || _MSC_VER >= 1700
748  // Attempts to dequeue an element; if the queue is empty,
749  // waits until an element is available up to the specified timeout,
750  // then dequeues it and returns true, or returns false if the timeout
751  // expires before an element can be dequeued.
752  // Using a negative timeout indicates an indefinite timeout,
753  // and is thus functionally equivalent to calling wait_dequeue.
754  template <typename U, typename Rep, typename Period>
755  inline bool wait_dequeue_timed(
756  U& result, std::chrono::duration<Rep, Period> const& timeout) {
757  return wait_dequeue_timed(
758  result,
759  std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
760  }
761 #endif
762 
763  // Returns a pointer to the front element in the queue (the one that
764  // would be removed next by a call to `try_dequeue` or `pop`). If the
765  // queue appears empty at the time the method is called, nullptr is
766  // returned instead.
767  // Must be called only from the consumer thread.
768  AE_FORCEINLINE T* peek() { return inner.peek(); }
769 
770  // Removes the front element from the queue, if any, without returning it.
771  // Returns true on success, or false if the queue appeared empty at the time
772  // `pop` was called.
774  if (sema.tryWait()) {
775  bool result = inner.pop();
776  assert(result);
777  AE_UNUSED(result);
778  return true;
779  }
780  return false;
781  }
782 
783  // Returns the approximate number of items currently in the queue.
784  // Safe to call from both the producer and consumer threads.
785  AE_FORCEINLINE size_t size_approx() const { return sema.availableApprox(); }
786 
787  private:
788  // Disable copying & assignment
790  BlockingReaderWriterQueue& operator=(ReaderWriterQueue const&) {}
791 
792  private:
793  ReaderWriterQueue inner;
794  spsc_sema::LightweightSemaphore sema;
795 };
796 
797 } // end namespace moodycamel
798 
799 #ifdef AE_VCPP
800 #pragma warning(pop)
801 #endif
#define AE_UNUSED(x)
Definition: atomicops.h:45
#define AE_FORCEINLINE
Definition: atomicops.h:54
Definition: readerwriterqueue.h:653
AE_FORCEINLINE bool enqueue(T const &element)
Definition: readerwriterqueue.h:685
AE_FORCEINLINE bool try_enqueue(T &&element)
Definition: readerwriterqueue.h:674
AE_FORCEINLINE size_t size_approx() const
Definition: readerwriterqueue.h:785
AE_FORCEINLINE bool pop()
Definition: readerwriterqueue.h:773
AE_FORCEINLINE T * peek()
Definition: readerwriterqueue.h:768
void wait_dequeue(U &result)
Definition: readerwriterqueue.h:721
AE_FORCEINLINE bool try_enqueue(T const &element)
Definition: readerwriterqueue.h:663
BlockingReaderWriterQueue(size_t maxSize=15)
Definition: readerwriterqueue.h:658
bool try_dequeue(U &result)
Definition: readerwriterqueue.h:708
bool wait_dequeue_timed(U &result, std::int64_t timeout_usecs)
Definition: readerwriterqueue.h:736
AE_FORCEINLINE bool enqueue(T &&element)
Definition: readerwriterqueue.h:696
Definition: readerwriterqueue.h:55
size_t size_approx() const
Definition: readerwriterqueue.h:414
bool pop()
Definition: readerwriterqueue.h:347
ReaderWriterQueue(size_t maxSize=15)
Definition: readerwriterqueue.h:81
AE_FORCEINLINE bool try_enqueue(T const &element)
Definition: readerwriterqueue.h:177
T * peek()
Definition: readerwriterqueue.h:306
AE_FORCEINLINE bool enqueue(T const &element)
Definition: readerwriterqueue.h:191
AE_FORCEINLINE bool enqueue(T &&element)
Definition: readerwriterqueue.h:198
AE_FORCEINLINE bool try_enqueue(T &&element)
Definition: readerwriterqueue.h:184
bool try_dequeue(U &result)
Definition: readerwriterqueue.h:206
~ReaderWriterQueue()
Definition: readerwriterqueue.h:148
bool tryWait()
Definition: atomicops.h:619
void wait()
Definition: atomicops.h:627
ssize_t availableApprox() const
Definition: atomicops.h:644
void signal(ssize_t count=1)
Definition: atomicops.h:635
AE_FORCEINLINE T load() const
Definition: atomicops.h:343
Definition: atomicops.h:69
@ memory_order_acquire
Definition: atomicops.h:73
@ memory_order_sync
Definition: atomicops.h:80
@ memory_order_release
Definition: atomicops.h:74
AE_FORCEINLINE void fence(memory_order order)
Definition: atomicops.h:222
AE_FORCEINLINE void compiler_fence(memory_order order)
Definition: atomicops.h:201
#define MOODYCAMEL_CACHE_LINE_SIZE
Definition: readerwriterqueue.h:33