123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- // Copyright 2015 Dolphin Emulator Project
- // Licensed under GPLv2+
- // Refer to the license.txt file included.
- #pragma once
- #include <atomic>
- #include <mutex>
- #include <thread>
- #include "Common/Event.h"
- #include "Common/Flag.h"
- namespace Common
- {
- // This class provides a synchronized loop.
- // It's a thread-safe way to trigger a new iteration without busy loops.
- // It's optimized for high-usage iterations which usually are already running while it's triggered often.
- // Be careful when using Wait() and Wakeup() at the same time. Wait() may block forever while Wakeup() is called regularly.
- class BlockingLoop
- {
- public:
- BlockingLoop()
- {
- m_stopped.Set();
- }
- ~BlockingLoop()
- {
- Stop();
- }
- // Triggers to rerun the payload of the Run() function at least once again.
- // This function will never block and is designed to finish as fast as possible.
- void Wakeup()
- {
- // Already running, so no need for a wakeup.
- // This is the common case, so try to get this as fast as possible.
- if (m_running_state.load() >= STATE_NEED_EXECUTION)
- return;
- // Mark that new data is available. If the old state will rerun the payload
- // itself, we don't have to set the event to interrupt the worker.
- if (m_running_state.exchange(STATE_NEED_EXECUTION) != STATE_SLEEPING)
- return;
- // Else as the worker thread may sleep now, we have to set the event.
- m_new_work_event.Set();
- }
- // Wait for a complete payload run after the last Wakeup() call.
- // If stopped, this returns immediately.
- void Wait()
- {
- // already done
- if (IsDone())
- return;
- // notifying this event will only wake up one thread, so use a mutex here to
- // allow only one waiting thread. And in this way, we get an event free wakeup
- // but for the first thread for free
- std::lock_guard<std::mutex> lk(m_wait_lock);
- // Wait for the worker thread to finish.
- while (!IsDone())
- {
- m_done_event.Wait();
- }
- // As we wanted to wait for the other thread, there is likely no work remaining.
- // So there is no need for a busy loop any more.
- m_may_sleep.Set();
- }
- // Half start the worker.
- // So this object is in a running state and Wait() will block until the worker calls Run().
- // This may be called from any thread and is supposed to be called at least once before Wait() is used.
- void Prepare()
- {
- // There is a race condition if the other threads call this function while
- // the loop thread is initializing. Using this lock will ensure a valid state.
- std::lock_guard<std::mutex> lk(m_prepare_lock);
- if (!m_stopped.TestAndClear())
- return;
- m_running_state.store(STATE_LAST_EXECUTION); // so the payload will only be executed once without any Wakeup call
- m_shutdown.Clear();
- m_may_sleep.Set();
- }
- // Main loop of this object.
- // The payload callback is called at least as often as it's needed to match the Wakeup() requirements.
- // The optional timeout parameter is a timeout for how periodically the payload should be called.
- // Use timeout = 0 to run without a timeout at all.
- template<class F> void Run(F payload, int64_t timeout = 0)
- {
- // Asserts that Prepare is called at least once before we enter the loop.
- // But a good implementation should call this before already.
- Prepare();
- while (!m_shutdown.IsSet())
- {
- payload();
- switch (m_running_state.load())
- {
- case STATE_NEED_EXECUTION:
- // We won't get notified while we are in the STATE_NEED_EXECUTION state, so maybe Wakeup was called.
- // So we have to assume on finishing the STATE_NEED_EXECUTION state, that there may be some remaining tasks.
- // To process this tasks, we call the payload again within the STATE_LAST_EXECUTION state.
- m_running_state--;
- break;
- case STATE_LAST_EXECUTION:
- // If we're still in the STATE_LAST_EXECUTION state, then Wakeup wasn't called within the last
- // execution of the payload. This means we should be ready now.
- // But bad luck, Wakeup may have been called right now. So break and rerun the payload
- // if the state was touched.
- if (m_running_state-- != STATE_LAST_EXECUTION)
- break;
- // Else we're likely in the STATE_DONE state now, so wakeup the waiting threads right now.
- // However, if we're not in the STATE_DONE state any more, the event should also be
- // triggered so that we'll skip the next waiting call quite fast.
- m_done_event.Set();
- case STATE_DONE:
- // We're done now. So time to check if we want to sleep or if we want to stay in a busy loop.
- if (m_may_sleep.TestAndClear())
- {
- // Try to set the sleeping state.
- if (m_running_state-- != STATE_DONE)
- break;
- }
- else
- {
- // Busy loop.
- break;
- }
- case STATE_SLEEPING:
- // Just relax
- if (timeout > 0)
- {
- m_new_work_event.WaitFor(std::chrono::milliseconds(timeout));
- }
- else
- {
- m_new_work_event.Wait();
- }
- break;
- }
- }
- // Shutdown down, so get a safe state
- m_running_state.store(STATE_DONE);
- m_stopped.Set();
- // Wake up the last Wait calls.
- m_done_event.Set();
- }
- // Quits the main loop.
- // By default, it will wait until the main loop quits.
- // Be careful to not use the blocking way within the payload of the Run() method.
- void Stop(bool block = true)
- {
- if (m_stopped.IsSet())
- return;
- m_shutdown.Set();
- // We have to interrupt the sleeping call to let the worker shut down soon.
- Wakeup();
- if (block)
- Wait();
- }
- bool IsRunning() const
- {
- return !m_stopped.IsSet() && !m_shutdown.IsSet();
- }
- bool IsDone() const
- {
- return m_stopped.IsSet() || m_running_state.load() <= STATE_DONE;
- }
- // This function should be triggered regularly over time so
- // that we will fall back from the busy loop to sleeping.
- void AllowSleep()
- {
- m_may_sleep.Set();
- }
- private:
- std::mutex m_wait_lock;
- std::mutex m_prepare_lock;
- Flag m_stopped; // If this is set, Wait() shall not block.
- Flag m_shutdown; // If this is set, the loop shall end.
- Event m_new_work_event;
- Event m_done_event;
- enum RUNNING_TYPE {
- STATE_SLEEPING = 0,
- STATE_DONE = 1,
- STATE_LAST_EXECUTION = 2,
- STATE_NEED_EXECUTION = 3
- };
- std::atomic<int> m_running_state; // must be of type RUNNING_TYPE
- Flag m_may_sleep; // If this is set, we fall back from the busy loop to an event based synchronization.
- };
- }
|