concurrent_ring_buffer.h 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // Copyright 2014 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #pragma once
  5. #include <array>
  6. #include <condition_variable>
  7. #include <cstdint>
  8. #include <mutex>
  9. #include <thread>
  10. #include "common/common.h" // for NonCopyable
  11. namespace Common {
  12. /**
  13. * A MPMC (Multiple-Producer Multiple-Consumer) concurrent ring buffer. This data structure permits
  14. * multiple threads to push and pop from a queue of bounded size.
  15. */
  16. template <typename T, size_t ArraySize>
  17. class ConcurrentRingBuffer : private NonCopyable {
  18. public:
  19. /// Value returned by the popping functions when the queue has been closed.
  20. static const size_t QUEUE_CLOSED = -1;
  21. ConcurrentRingBuffer() {}
  22. ~ConcurrentRingBuffer() {
  23. // If for whatever reason the queue wasn't completely drained, destroy the left over items.
  24. for (size_t i = reader_index, end = writer_index; i != end; i = (i + 1) % ArraySize) {
  25. Data()[i].~T();
  26. }
  27. }
  28. /**
  29. * Pushes a value to the queue. If the queue is full, this method will block. Does nothing if
  30. * the queue is closed.
  31. */
  32. void Push(T val) {
  33. std::unique_lock<std::mutex> lock(mutex);
  34. if (closed) {
  35. return;
  36. }
  37. // If the buffer is full, wait
  38. writer.wait(lock, [&]{
  39. return (writer_index + 1) % ArraySize != reader_index;
  40. });
  41. T* item = &Data()[writer_index];
  42. new (item) T(std::move(val));
  43. writer_index = (writer_index + 1) % ArraySize;
  44. // Wake up waiting readers
  45. lock.unlock();
  46. reader.notify_one();
  47. }
  48. /**
  49. * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will not
  50. * block, and might return 0 values if there are no elements in the queue when it is called.
  51. *
  52. * @return The number of elements stored in `dest`. If the queue has been closed, returns
  53. * `QUEUE_CLOSED`.
  54. */
  55. size_t Pop(T* dest, size_t dest_len) {
  56. std::unique_lock<std::mutex> lock(mutex);
  57. if (closed && !CanRead()) {
  58. return QUEUE_CLOSED;
  59. }
  60. return PopInternal(dest, dest_len);
  61. }
  62. /**
  63. * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will block
  64. * if there are no elements in the queue when it is called.
  65. *
  66. * @return The number of elements stored in `dest`. If the queue has been closed, returns
  67. * `QUEUE_CLOSED`.
  68. */
  69. size_t BlockingPop(T* dest, size_t dest_len) {
  70. std::unique_lock<std::mutex> lock(mutex);
  71. if (closed && !CanRead()) {
  72. return QUEUE_CLOSED;
  73. }
  74. while (!CanRead()) {
  75. reader.wait(lock);
  76. if (closed && !CanRead()) {
  77. return QUEUE_CLOSED;
  78. }
  79. }
  80. DEBUG_ASSERT(CanRead());
  81. return PopInternal(dest, dest_len);
  82. }
  83. /**
  84. * Closes the queue. After calling this method, `Push` operations won't have any effect, and
  85. * `PopMany` and `PopManyBlock` will start returning `QUEUE_CLOSED`. This is intended to allow
  86. * a graceful shutdown of all consumers.
  87. */
  88. void Close() {
  89. std::unique_lock<std::mutex> lock(mutex);
  90. closed = true;
  91. // We need to wake up any reader that are waiting for an item that will never come.
  92. lock.unlock();
  93. reader.notify_all();
  94. }
  95. /// Returns true if `Close()` has been called.
  96. bool IsClosed() const {
  97. return closed;
  98. }
  99. private:
  100. size_t PopInternal(T* dest, size_t dest_len) {
  101. size_t output_count = 0;
  102. while (output_count < dest_len && CanRead()) {
  103. DEBUG_ASSERT(CanRead());
  104. T* item = &Data()[reader_index];
  105. T out_val = std::move(*item);
  106. item->~T();
  107. size_t prev_index = (reader_index + ArraySize - 1) % ArraySize;
  108. reader_index = (reader_index + 1) % ArraySize;
  109. if (writer_index == prev_index) {
  110. writer.notify_one();
  111. }
  112. dest[output_count++] = std::move(out_val);
  113. }
  114. return output_count;
  115. }
  116. bool CanRead() const {
  117. return reader_index != writer_index;
  118. }
  119. T* Data() {
  120. return static_cast<T*>(static_cast<void*>(&storage));
  121. }
  122. /// Storage for entries
  123. typename std::aligned_storage<ArraySize * sizeof(T),
  124. std::alignment_of<T>::value>::type storage;
  125. /// Data is valid in the half-open interval [reader, writer). If they are `QUEUE_CLOSED` then the
  126. /// queue has been closed.
  127. size_t writer_index = 0, reader_index = 0;
  128. // True if the queue has been closed.
  129. bool closed = false;
  130. /// Mutex that protects the entire data structure.
  131. std::mutex mutex;
  132. /// Signaling wakes up reader which is waiting for storage to be non-empty.
  133. std::condition_variable reader;
  134. /// Signaling wakes up writer which is waiting for storage to be non-full.
  135. std::condition_variable writer;
  136. };
  137. } // namespace