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


ur_modern_driver
Author(s): Thomas Timm Andersen, Simon Rasmussen
autogenerated on Fri Jun 26 2020 03:37:00