1 #pragma once 2 3 #include <atomic> 4 #include <condition_variable> 5 #include <mutex> 6 7 #include <QThread> 8 9 #include "util/logger.h" 10 11 12 /// A worker thread without an event loop. 13 /// 14 /// This object lives in the creating thread of the host, i.e. does not 15 /// run its own event loop. It does not use slots for communication 16 /// with its host which would otherwise still be executed in the host's 17 /// thread. 18 /// 19 /// Signals emitted from the internal worker thread by derived classes 20 /// will queued connections. Communication in the opposite direction is 21 /// accomplished by using lock-free types to avoid locking the host 22 /// thread through priority inversion. Lock-free types might also used 23 /// for any shared state that is read from the host thread after being 24 /// notified about changes. 25 /// 26 /// Derived classes or their owners are responsible to start the thread 27 /// with the desired priority. 28 class WorkerThread : public QThread { 29 Q_OBJECT 30 31 public: 32 explicit WorkerThread( 33 const QString& name = QString(), 34 const QThread::Priority priority = QThread::InheritPriority); 35 /// The destructor must be triggered by calling deleteLater() to 36 /// ensure that the thread has already finished and is not running 37 /// while destroyed! Connect finished() to deleteAfter() and then 38 /// call stop() on the running worker thread explicitly to trigger 39 /// the destruction. Use deleteAfterFinished() for this purpose. 40 ~WorkerThread() override; 41 42 void deleteAfterFinished(); 43 name()44 const QString& name() const { 45 return m_name; 46 } 47 48 /// Commands the thread to suspend itself asap. 49 /// 50 /// Must not be invoked from the worker thread itself to 51 /// avoid race conditions! 52 void suspend(); 53 54 /// Resumes a suspended thread by waking it up. 55 /// 56 /// Must not be invoked from the worker thread itself to 57 /// avoid race conditions! 58 void resume(); 59 60 /// Wakes up a sleeping thread. If the thread has been suspended 61 /// it will fall asleep again. A suspended thread needs to be 62 /// resumed. 63 /// 64 /// Must not be invoked from the worker thread itself to 65 /// avoid race conditions! 66 void wake(); 67 68 /// Commands the thread to stop asap. This action is irreversible, 69 /// i.e. the thread cannot be restarted once it has been stopped. 70 /// 71 /// Must not be invoked from the worker thread itself to 72 /// avoid race conditions! 73 void stop(); 74 75 /// Non-blocking atomic read of the stop flag which indicates that 76 /// the thread is stopping, i.e. it will soon exit or already has 77 /// exited the run loop. isStopping()78 bool isStopping() const { 79 return m_stop.load(); 80 } 81 82 protected: 83 void run() final; 84 85 /// The internal run loop. Not to be confused with the Qt event 86 /// loop since the worker thread doesn't have one! 87 /// An implementation may exit this loop after all work is done, 88 /// which in turn exits and terminates the thread. The loop should 89 /// also be left asap when isStopping() returns true. This condition 90 /// should be checked repeatedly during execution of the loop and 91 /// especially before starting any expensive subtasks. 92 virtual void doRun() = 0; 93 94 enum class TryFetchWorkItemsResult { 95 Ready, 96 Idle, 97 }; 98 99 /// Non-blocking function that determines whether the worker thread 100 /// is idle (i.e. no new tasks have been scheduled) and should be 101 /// either suspended until resumed or put to sleep until woken up. 102 /// 103 /// Implementing classes are able to control what to do if no more 104 /// work is currently available. Returning TryFetchWorkItemsResult::Idle 105 /// preserves the current suspend state and lets the thread sleep 106 /// until wake() is called. 107 /// 108 /// Implementing classes are responsible for storing the fetched 109 /// work items internally for later processing during 110 /// doRun(). 111 /// 112 /// The stop flag does not have to be checked when entering this function, 113 /// because it has already been checked just before the invocation. Though 114 /// the fetch operation may check again before starting any expensive 115 /// internal subtask. 116 virtual TryFetchWorkItemsResult tryFetchWorkItems() = 0; 117 118 /// Blocks while idle and not stopped. Returns true when new work items 119 /// for processing have been fetched and false if the thread has been 120 /// stopped while waiting. 121 bool awaitWorkItemsFetched(); 122 123 /// Blocks the worker thread while the suspend flag is set. 124 /// 125 /// Worker threads should invoke this yield-like method periodically. 126 /// Especially before starting any computational intensive work that 127 /// is expected to take some time. 128 /// 129 /// After returning from this function worker threads should also 130 /// check isStopping() and exit if they have been asked to stop. 131 /// 132 /// This function must not be called from tryFetchWorkItems() 133 /// to avoid a deadlock on the non-recursive mutex! 134 void sleepWhileSuspended(); 135 136 private: 137 const QString m_name; 138 const QThread::Priority m_priority; 139 140 const mixxx::Logger m_logger; 141 142 std::atomic<bool> m_suspend; 143 std::atomic<bool> m_stop; 144 145 std::mutex m_sleepMutex; 146 std::condition_variable m_sleepWaitCond; 147 }; 148