threadsafe_queue.h 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. // Copyright 2010 Dolphin Emulator Project
  2. // Licensed under GPLv2+
  3. // Refer to the license.txt file included.
  4. #pragma once
  5. // a simple lockless thread-safe,
  6. // single reader, single writer queue
  7. #include <atomic>
  8. #include <condition_variable>
  9. #include <cstddef>
  10. #include <mutex>
  11. #include <utility>
  12. namespace Common {
  13. template <typename T, bool with_stop_token = false>
  14. class SPSCQueue {
  15. public:
  16. SPSCQueue() {
  17. write_ptr = read_ptr = new ElementPtr();
  18. }
  19. ~SPSCQueue() {
  20. // this will empty out the whole queue
  21. delete read_ptr;
  22. }
  23. [[nodiscard]] std::size_t Size() const {
  24. return size.load();
  25. }
  26. [[nodiscard]] bool Empty() const {
  27. return Size() == 0;
  28. }
  29. [[nodiscard]] T& Front() const {
  30. return read_ptr->current;
  31. }
  32. template <typename Arg>
  33. void Push(Arg&& t) {
  34. // create the element, add it to the queue
  35. write_ptr->current = std::forward<Arg>(t);
  36. // set the next pointer to a new element ptr
  37. // then advance the write pointer
  38. ElementPtr* new_ptr = new ElementPtr();
  39. write_ptr->next.store(new_ptr, std::memory_order_release);
  40. write_ptr = new_ptr;
  41. ++size;
  42. // cv_mutex must be held or else there will be a missed wakeup if the other thread is in the
  43. // line before cv.wait
  44. // TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
  45. // See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
  46. std::lock_guard lock{cv_mutex};
  47. cv.notify_one();
  48. }
  49. void Pop() {
  50. --size;
  51. ElementPtr* tmpptr = read_ptr;
  52. // advance the read pointer
  53. read_ptr = tmpptr->next.load();
  54. // set the next element to nullptr to stop the recursive deletion
  55. tmpptr->next.store(nullptr);
  56. delete tmpptr; // this also deletes the element
  57. }
  58. bool Pop(T& t) {
  59. if (Empty())
  60. return false;
  61. --size;
  62. ElementPtr* tmpptr = read_ptr;
  63. read_ptr = tmpptr->next.load(std::memory_order_acquire);
  64. t = std::move(tmpptr->current);
  65. tmpptr->next.store(nullptr);
  66. delete tmpptr;
  67. return true;
  68. }
  69. void Wait() {
  70. if (Empty()) {
  71. std::unique_lock lock{cv_mutex};
  72. cv.wait(lock, [this] { return !Empty(); });
  73. }
  74. }
  75. T PopWait() {
  76. Wait();
  77. T t;
  78. Pop(t);
  79. return t;
  80. }
  81. T PopWait(std::stop_token stop_token) {
  82. if (Empty()) {
  83. std::unique_lock lock{cv_mutex};
  84. cv.wait(lock, stop_token, [this] { return !Empty(); });
  85. }
  86. if (stop_token.stop_requested()) {
  87. return T{};
  88. }
  89. T t;
  90. Pop(t);
  91. return t;
  92. }
  93. // not thread-safe
  94. void Clear() {
  95. size.store(0);
  96. delete read_ptr;
  97. write_ptr = read_ptr = new ElementPtr();
  98. }
  99. private:
  100. // stores a pointer to element
  101. // and a pointer to the next ElementPtr
  102. class ElementPtr {
  103. public:
  104. ElementPtr() {}
  105. ~ElementPtr() {
  106. ElementPtr* next_ptr = next.load();
  107. if (next_ptr)
  108. delete next_ptr;
  109. }
  110. T current;
  111. std::atomic<ElementPtr*> next{nullptr};
  112. };
  113. ElementPtr* write_ptr;
  114. ElementPtr* read_ptr;
  115. std::atomic_size_t size{0};
  116. std::mutex cv_mutex;
  117. std::conditional_t<with_stop_token, std::condition_variable_any, std::condition_variable> cv;
  118. };
  119. // a simple thread-safe,
  120. // single reader, multiple writer queue
  121. template <typename T, bool with_stop_token = false>
  122. class MPSCQueue {
  123. public:
  124. [[nodiscard]] std::size_t Size() const {
  125. return spsc_queue.Size();
  126. }
  127. [[nodiscard]] bool Empty() const {
  128. return spsc_queue.Empty();
  129. }
  130. [[nodiscard]] T& Front() const {
  131. return spsc_queue.Front();
  132. }
  133. template <typename Arg>
  134. void Push(Arg&& t) {
  135. std::lock_guard lock{write_lock};
  136. spsc_queue.Push(t);
  137. }
  138. void Pop() {
  139. return spsc_queue.Pop();
  140. }
  141. bool Pop(T& t) {
  142. return spsc_queue.Pop(t);
  143. }
  144. void Wait() {
  145. spsc_queue.Wait();
  146. }
  147. T PopWait() {
  148. return spsc_queue.PopWait();
  149. }
  150. T PopWait(std::stop_token stop_token) {
  151. return spsc_queue.PopWait(stop_token);
  152. }
  153. // not thread-safe
  154. void Clear() {
  155. spsc_queue.Clear();
  156. }
  157. private:
  158. SPSCQueue<T, with_stop_token> spsc_queue;
  159. std::mutex write_lock;
  160. };
  161. } // namespace Common