concurrent_ring_buffer.h 5.0 KB

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