Skip to content

Commit ad23fa1

Browse files
authored
Merge pull request #77 from SLM-Audio/syl/thread-test-changes
Thread refactor
2 parents 3e4da96 + 635370a commit ad23fa1

File tree

8 files changed

+276
-182
lines changed

8 files changed

+276
-182
lines changed

include/mostly_harmless/utils/mostlyharmless_TaskThread.h

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,74 @@
1010
#include <thread>
1111

1212
namespace mostly_harmless::utils {
13+
/***
14+
* \brief Convenience wrapper around std::thread
15+
*
16+
* Contains some simple signalling mechanisms, and forms the basis for `Timer` - to use it, supply a lambda to the `action` field, then call perform (and remember to call `stop` to join the thread!)
17+
*/
1318
class TaskThread {
1419
public:
20+
/**
21+
* Destructor. Calls stop, which joins the internal thread, so will block if the thread is running!
22+
*/
1523
~TaskThread() noexcept;
16-
void perform();
17-
void stop(bool join) noexcept;
18-
void sleep();
19-
void wake();
20-
[[nodiscard]] bool isThreadRunning() const noexcept;
24+
25+
/**
26+
* Performs the `action` lambda (if it's been set) on a std::thread, and sets isThreadRunning to false on scope exit.
27+
*/
28+
auto perform() -> void;
29+
30+
/**
31+
* Calls signalStop(), joins the thread, and resets the internal state for reuse.
32+
*/
33+
auto stop() noexcept -> void;
34+
35+
/**
36+
* For use in your `action` lambda, pauses the thread's execution until it's woken via wake().
37+
* Internally just uses a `std::condition_variable` and an atomic "canWakeUp" bool - this call is equivalent to std::condition_variable::wait().
38+
* Note that this doesn't actually do anything on its own unless you call `sleep` in your action - it's just there if you need it!
39+
*/
40+
auto sleep() -> void;
41+
42+
/**
43+
* Paired with sleep, sets the `canWakeUp` bool to true, and notifies the underlying condition variable. All ramblings from wake()` also apply here.
44+
*/
45+
auto wake() -> void;
46+
47+
/**
48+
* Sets an internal atomic bool that the thread should exit. Like sleep() and wake(), doesn't actually do anything on its own,
49+
* pair this with usage of hasSignalledStop() in your custom thread action.
50+
*/
51+
auto signalStop() -> void;
52+
53+
/**
54+
* Retrieves the state of the atomic bool described in signalStop() - use this in your custom thread action to respond to cancellation requests if needed (again, does nothing on it's own!)
55+
* @return The state of the `should_stop` bool.
56+
*/
57+
[[nodiscard]] auto hasSignalledStop() const noexcept -> bool;
58+
59+
/**
60+
* Gives a loose indication of whether the thread is still running or not - loose, because this will only get set *after* the user action has been run, not within.
61+
* This means there may be a few cpu cycles discrepancy between when you *think* this should return true and when it *actually* returns true.
62+
* TLDR, don't rely on it for anything timing critical!
63+
* @return Whether the thread is running or not.
64+
*/
65+
[[nodiscard]] auto isThreadRunning() const noexcept -> bool;
66+
67+
/**
68+
* A user-settable lambda to be invoked on the internal std::thread. Put your threaded logic here!!
69+
*/
2170
std::function<void(void)> action{ nullptr };
2271

2372
private:
24-
std::mutex m_mutex;
73+
auto reset() -> void;
74+
struct {
75+
std::mutex mutex;
76+
std::atomic<bool> canWakeUp{ false };
77+
std::condition_variable conditionVariable;
78+
} m_sleepState;
2579
std::atomic<bool> m_isThreadRunning{ false };
26-
std::atomic<bool> m_canWakeUp{ false };
27-
std::condition_variable m_conditionVariable;
80+
std::atomic<bool> m_stop{ false };
2881
std::unique_ptr<std::thread> m_thread{ nullptr };
2982
};
3083
} // namespace mostly_harmless::utils

include/mostly_harmless/utils/mostlyharmless_Timer.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
namespace mostly_harmless::utils {
1010
class Timer final {
1111
public:
12-
void run(int intervalMs);
13-
void run(double frequency);
14-
void stop(bool join);
12+
auto run(int intervalMs) -> void;
13+
auto run(double frequency) -> void;
14+
auto stop() -> void;
1515
std::function<void(void)> action;
1616

1717
private:

source/mostlyharmless_PluginBase.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ namespace mostly_harmless::internal {
386386

387387
void PluginBase::guiDestroy() noexcept {
388388
MH_LOG("GUI: guiDestroy()");
389-
m_guiDispatchThread.stop(true);
389+
m_guiDispatchThread.stop();
390390
m_editor.reset();
391391
}
392392

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,71 @@
11
//
22
// Created by Syl on 12/08/2024.
33
//
4+
#include "mostly_harmless/utils/mostlyharmless_OnScopeExit.h"
5+
6+
47
#include <mostly_harmless/utils/mostlyharmless_TaskThread.h>
58
#include <cassert>
69
#include <thread>
710
namespace mostly_harmless::utils {
811
TaskThread::~TaskThread() noexcept {
9-
stop(true);
12+
stop();
1013
}
1114

12-
void TaskThread::perform() {
15+
auto TaskThread::perform() -> void {
1316
auto expected{ false };
1417
if (m_isThreadRunning.compare_exchange_strong(expected, true)) {
1518
auto actionWrapper = [this]() -> void {
19+
OnScopeExit se{ [this]() -> void {
20+
m_isThreadRunning.store(false);
21+
} };
1622
action();
17-
m_isThreadRunning = false;
1823
};
1924
m_thread = std::make_unique<std::thread>(std::move(actionWrapper));
2025
}
2126
}
2227

23-
void TaskThread::stop(bool join) noexcept {
24-
m_isThreadRunning = false;
28+
auto TaskThread::stop() noexcept -> void {
29+
signalStop();
2530
if (!m_thread) {
2631
return;
2732
}
28-
if (join) {
29-
if (m_thread->joinable()) {
30-
m_thread->join();
31-
}
32-
m_thread.reset();
33+
if (m_thread->joinable()) {
34+
m_thread->join();
3335
}
36+
reset();
3437
}
3538

36-
void TaskThread::sleep() {
37-
m_canWakeUp = false;
38-
std::unique_lock<std::mutex> ul{ m_mutex };
39-
m_conditionVariable.wait(ul, [this]() -> bool { return m_canWakeUp; });
39+
auto TaskThread::sleep() -> void {
40+
m_sleepState.canWakeUp = false;
41+
std::unique_lock<std::mutex> ul{ m_sleepState.mutex };
42+
m_sleepState.conditionVariable.wait(ul, [this]() -> bool { return m_sleepState.canWakeUp; });
4043
}
4144

42-
void TaskThread::wake() {
43-
m_canWakeUp = true;
44-
std::lock_guard<std::mutex> lock{ m_mutex };
45-
m_conditionVariable.notify_one();
45+
auto TaskThread::wake() -> void {
46+
m_sleepState.canWakeUp = true;
47+
std::lock_guard<std::mutex> lock{ m_sleepState.mutex };
48+
m_sleepState.conditionVariable.notify_one();
4649
}
4750

48-
bool TaskThread::isThreadRunning() const noexcept {
51+
auto TaskThread::isThreadRunning() const noexcept -> bool {
4952
return m_isThreadRunning;
5053
}
5154

55+
auto TaskThread::signalStop() -> void {
56+
m_stop.store(true);
57+
}
58+
59+
auto TaskThread::hasSignalledStop() const noexcept -> bool {
60+
return m_stop;
61+
}
62+
63+
auto TaskThread::reset() -> void {
64+
m_thread.reset();
65+
m_stop = false;
66+
}
67+
68+
69+
70+
5271
} // namespace mostly_harmless::utils

source/utils/mostlyharmless_Timer.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
#include <mostly_harmless/utils/mostlyharmless_Timer.h>
55
#include <thread>
66
namespace mostly_harmless::utils {
7-
void Timer::run(int intervalMs) {
7+
auto Timer::run(int intervalMs) -> void {
88
if (!action || m_thread.isThreadRunning()) return;
99
auto threadAction = [this, intervalMs]() -> void {
1010
auto startPoint = std::chrono::steady_clock::now();
11-
while (m_thread.isThreadRunning()) {
11+
while (!m_thread.hasSignalledStop()) {
1212
std::this_thread::sleep_for(std::chrono::milliseconds(1));
1313
const auto now = std::chrono::steady_clock::now();
1414
const auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(now - startPoint);
@@ -21,13 +21,13 @@ namespace mostly_harmless::utils {
2121
m_thread.perform();
2222
}
2323

24-
void Timer::run(double frequency) {
24+
auto Timer::run(double frequency) -> void {
2525
const auto intervalMs = static_cast<int>(1.0 / frequency);
2626
run(intervalMs);
2727
}
2828

29-
void Timer::stop(bool join) {
30-
m_thread.stop(join);
29+
auto Timer::stop() -> void {
30+
m_thread.stop();
3131
}
3232

3333

0 commit comments

Comments
 (0)