diff --git a/core/AmThread.cpp b/core/AmThread.cpp index 54dff29d..31d837b7 100644 --- a/core/AmThread.cpp +++ b/core/AmThread.cpp @@ -55,16 +55,19 @@ void AmThread::_start() DBG("Thread %lu is ending.\n", _pid); + std::lock_guard _l(run_mut); _state = state::stopped; } void AmThread::start() { - state expected = state::idle; - if(!_state.compare_exchange_strong(expected, state::running)) { + std::lock_guard _l(run_mut); + + if (_state != state::idle) { DBG("Thread %lu already running.\n", _pid); return; } + _state = state::running; _pid = 0; @@ -87,11 +90,17 @@ void AmThread::ready() void AmThread::stop() { - state expected = state::running; - if(!_state.compare_exchange_strong(expected, state::stopping)){ + std::unique_lock _l(run_mut); + + if (_state != state::running) { DBG("Thread %lu already stopped\n", _pid); return; } + _state = state::stopping; + + _l.unlock(); + + run_cond.notify_all(); // gives the thread a chance to clean up DBG("Thread %lu calling on_stop\n", _pid); @@ -101,18 +110,32 @@ void AmThread::stop() void AmThread::join() { + std::unique_lock _l(run_mut); + // don't attempt to join thread that doesn't exist if (_state == state::idle) return; - // make sure only one other thread joins this one. all others - // are made to wait through the mutex - std::lock_guard _l(_join_mt); - if (!_joined) { - if (_td.joinable()) - _td.join(); - _joined = true; + // nothing to do if already done + if (_joined == join_state::joined) + return; + + // is somebody else doing the joining? wait until done + while (_joined == join_state::joining) + run_cond.wait(_l); + + if (_joined == join_state::joined) + return; + + // we have to do the joining (state == unjoined) + if (_td.joinable()) { + _joined = join_state::joining; + _l.unlock(); + _td.join(); + _l.lock(); } + _joined = join_state::joined; + run_cond.notify_all(); } diff --git a/core/AmThread.h b/core/AmThread.h index 2b572535..e1d13723 100644 --- a/core/AmThread.h +++ b/core/AmThread.h @@ -123,10 +123,14 @@ class AmThread stopped, // after stop }; - std::atomic _state; + enum join_state { + unjoined, // nobody joining/joined yet + joining, // one thread is joining + joined, // has been joined + }; - std::mutex _join_mt; - bool _joined; + state _state; + join_state _joined; void _start(); @@ -134,11 +138,24 @@ class AmThread bool _triggers_ready; protected: + // protects `_state`, `_joined`, and anything else the subclass wants to use it for, + // primarily meant to determine whether a thread should run or not + std::mutex run_mut; + + // can be used as a waker for threads running in a loop + std::condition_variable run_cond; + virtual void run()=0; virtual void on_stop() {}; + /** @return true if this thread ought to stop, without obtaining run_mut. */ + bool stop_requested_unlocked() const { return _state == stopping; } + /** @return true if this thread ought to stop. */ - bool stop_requested() { return _state == stopping; } + bool stop_requested() { + std::lock_guard _l(run_mut); + return stop_requested_unlocked(); + } std::optional> _sd_notifier; @@ -150,7 +167,7 @@ public: AmThread(bool triggers_ready = false) : _state(state::idle), - _joined(false), + _joined(join_state::unjoined), _triggers_ready(triggers_ready), _pid(0) {} @@ -168,7 +185,10 @@ public: void join(); /** @return true if this thread has finished. */ - bool is_stopped() { return _state == stopped; } + bool is_stopped() { + std::lock_guard _l(run_mut); + return _state == stopped; + } }; /**