BlockingLoop.h 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // Copyright 2015 Dolphin Emulator Project
  2. // Licensed under GPLv2+
  3. // Refer to the license.txt file included.
  4. #pragma once
  5. #include <atomic>
  6. #include <mutex>
  7. #include <thread>
  8. #include "Common/Event.h"
  9. #include "Common/Flag.h"
  10. namespace Common
  11. {
  12. // This class provides a synchronized loop.
  13. // It's a thread-safe way to trigger a new iteration without busy loops.
  14. // It's optimized for high-usage iterations which usually are already running while it's triggered often.
  15. // Be careful when using Wait() and Wakeup() at the same time. Wait() may block forever while Wakeup() is called regularly.
  16. class BlockingLoop
  17. {
  18. public:
  19. BlockingLoop()
  20. {
  21. m_stopped.Set();
  22. }
  23. ~BlockingLoop()
  24. {
  25. Stop();
  26. }
  27. // Triggers to rerun the payload of the Run() function at least once again.
  28. // This function will never block and is designed to finish as fast as possible.
  29. void Wakeup()
  30. {
  31. // Already running, so no need for a wakeup.
  32. // This is the common case, so try to get this as fast as possible.
  33. if (m_running_state.load() >= STATE_NEED_EXECUTION)
  34. return;
  35. // Mark that new data is available. If the old state will rerun the payload
  36. // itself, we don't have to set the event to interrupt the worker.
  37. if (m_running_state.exchange(STATE_NEED_EXECUTION) != STATE_SLEEPING)
  38. return;
  39. // Else as the worker thread may sleep now, we have to set the event.
  40. m_new_work_event.Set();
  41. }
  42. // Wait for a complete payload run after the last Wakeup() call.
  43. // If stopped, this returns immediately.
  44. void Wait()
  45. {
  46. // already done
  47. if (IsDone())
  48. return;
  49. // notifying this event will only wake up one thread, so use a mutex here to
  50. // allow only one waiting thread. And in this way, we get an event free wakeup
  51. // but for the first thread for free
  52. std::lock_guard<std::mutex> lk(m_wait_lock);
  53. // Wait for the worker thread to finish.
  54. while (!IsDone())
  55. {
  56. m_done_event.Wait();
  57. }
  58. // As we wanted to wait for the other thread, there is likely no work remaining.
  59. // So there is no need for a busy loop any more.
  60. m_may_sleep.Set();
  61. }
  62. // Half start the worker.
  63. // So this object is in a running state and Wait() will block until the worker calls Run().
  64. // This may be called from any thread and is supposed to be called at least once before Wait() is used.
  65. void Prepare()
  66. {
  67. // There is a race condition if the other threads call this function while
  68. // the loop thread is initializing. Using this lock will ensure a valid state.
  69. std::lock_guard<std::mutex> lk(m_prepare_lock);
  70. if (!m_stopped.TestAndClear())
  71. return;
  72. m_running_state.store(STATE_LAST_EXECUTION); // so the payload will only be executed once without any Wakeup call
  73. m_shutdown.Clear();
  74. m_may_sleep.Set();
  75. }
  76. // Main loop of this object.
  77. // The payload callback is called at least as often as it's needed to match the Wakeup() requirements.
  78. // The optional timeout parameter is a timeout for how periodically the payload should be called.
  79. // Use timeout = 0 to run without a timeout at all.
  80. template<class F> void Run(F payload, int64_t timeout = 0)
  81. {
  82. // Asserts that Prepare is called at least once before we enter the loop.
  83. // But a good implementation should call this before already.
  84. Prepare();
  85. while (!m_shutdown.IsSet())
  86. {
  87. payload();
  88. switch (m_running_state.load())
  89. {
  90. case STATE_NEED_EXECUTION:
  91. // We won't get notified while we are in the STATE_NEED_EXECUTION state, so maybe Wakeup was called.
  92. // So we have to assume on finishing the STATE_NEED_EXECUTION state, that there may be some remaining tasks.
  93. // To process this tasks, we call the payload again within the STATE_LAST_EXECUTION state.
  94. m_running_state--;
  95. break;
  96. case STATE_LAST_EXECUTION:
  97. // If we're still in the STATE_LAST_EXECUTION state, then Wakeup wasn't called within the last
  98. // execution of the payload. This means we should be ready now.
  99. // But bad luck, Wakeup may have been called right now. So break and rerun the payload
  100. // if the state was touched.
  101. if (m_running_state-- != STATE_LAST_EXECUTION)
  102. break;
  103. // Else we're likely in the STATE_DONE state now, so wakeup the waiting threads right now.
  104. // However, if we're not in the STATE_DONE state any more, the event should also be
  105. // triggered so that we'll skip the next waiting call quite fast.
  106. m_done_event.Set();
  107. case STATE_DONE:
  108. // We're done now. So time to check if we want to sleep or if we want to stay in a busy loop.
  109. if (m_may_sleep.TestAndClear())
  110. {
  111. // Try to set the sleeping state.
  112. if (m_running_state-- != STATE_DONE)
  113. break;
  114. }
  115. else
  116. {
  117. // Busy loop.
  118. break;
  119. }
  120. case STATE_SLEEPING:
  121. // Just relax
  122. if (timeout > 0)
  123. {
  124. m_new_work_event.WaitFor(std::chrono::milliseconds(timeout));
  125. }
  126. else
  127. {
  128. m_new_work_event.Wait();
  129. }
  130. break;
  131. }
  132. }
  133. // Shutdown down, so get a safe state
  134. m_running_state.store(STATE_DONE);
  135. m_stopped.Set();
  136. // Wake up the last Wait calls.
  137. m_done_event.Set();
  138. }
  139. // Quits the main loop.
  140. // By default, it will wait until the main loop quits.
  141. // Be careful to not use the blocking way within the payload of the Run() method.
  142. void Stop(bool block = true)
  143. {
  144. if (m_stopped.IsSet())
  145. return;
  146. m_shutdown.Set();
  147. // We have to interrupt the sleeping call to let the worker shut down soon.
  148. Wakeup();
  149. if (block)
  150. Wait();
  151. }
  152. bool IsRunning() const
  153. {
  154. return !m_stopped.IsSet() && !m_shutdown.IsSet();
  155. }
  156. bool IsDone() const
  157. {
  158. return m_stopped.IsSet() || m_running_state.load() <= STATE_DONE;
  159. }
  160. // This function should be triggered regularly over time so
  161. // that we will fall back from the busy loop to sleeping.
  162. void AllowSleep()
  163. {
  164. m_may_sleep.Set();
  165. }
  166. private:
  167. std::mutex m_wait_lock;
  168. std::mutex m_prepare_lock;
  169. Flag m_stopped; // If this is set, Wait() shall not block.
  170. Flag m_shutdown; // If this is set, the loop shall end.
  171. Event m_new_work_event;
  172. Event m_done_event;
  173. enum RUNNING_TYPE {
  174. STATE_SLEEPING = 0,
  175. STATE_DONE = 1,
  176. STATE_LAST_EXECUTION = 2,
  177. STATE_NEED_EXECUTION = 3
  178. };
  179. std::atomic<int> m_running_state; // must be of type RUNNING_TYPE
  180. Flag m_may_sleep; // If this is set, we fall back from the busy loop to an event based synchronization.
  181. };
  182. }