Task::WaitComplete to avoid spinning

Mutex waiting for task complete. Waiting for a task to be finished, The destructor for the thread and thread pool now take less than 1% usage. 😎
This commit is contained in:
2025-04-10 21:28:06 -04:00
parent b4b476bd7f
commit af819dc758
5 changed files with 15 additions and 9 deletions

View File

@@ -1,5 +1,6 @@
#pragma once
#include <condition_variable>
#include <functional>
#include <memory>
#include <atomic>
@@ -13,10 +14,18 @@ namespace MultiThreading {
class MultiThreading::TaskBase {
protected:
std::condition_variable completed_condition;
std::atomic<bool> complete = false;
std::mutex mtx;
void MarkTaskComplete() { std::lock_guard<std::mutex> lock(mtx); complete = true; completed_condition.notify_all(); }
public:
/// @returns whether the task is finished.
[[nodiscard]] bool Complete() const { return complete; }
/// Condition variable waits for the task to be complete
/// @note There is no way to know if the task has been assigned, If you do this and that never happens the calling thread will wait forever.
void WaitComplete() { std::unique_lock<std::mutex> lock(mtx); completed_condition.wait(lock, [this]() { return complete.load(); }); }
virtual void Run() = 0;
public:
TaskBase() = default;
@@ -30,7 +39,7 @@ private:
private:
explicit Task(std::function<T()> callable, T* result = nullptr) : TaskBase(), result(result), callable(std::move(callable)) {}
public:
void Run() final { result ? *result = callable() : callable(); complete = true; }
void Run() final { result ? *result = callable() : callable(); MarkTaskComplete(); }
public:
/// Create a Task. This is non-negotiable, This ensures that there's no issues related to the stack frame the task is on being popped before or while the job runs.
/// @param callable The function to run, *usually a lambda or std::bind*
@@ -47,7 +56,7 @@ private:
std::function<void()> callable = nullptr;
explicit Task(std::function<void()> callable) : TaskBase(), callable(std::move(callable)) {}
public:
void Run() final { callable(); complete = true; }
void Run() final { callable(); MarkTaskComplete(); }
/// Create a Task. This is non-negotiable, This ensures that there's no issues related to the stack frame the task is on being popped before or while the job runs.
/// @param callable The function to run, *usually a lambda or std::bind*

View File

@@ -1,9 +1,6 @@
#pragma once
#include <mutex>
#include <thread>
#include <functional>
#include <condition_variable>
#include <MultiThreading/Task.h>
namespace MultiThreading {
@@ -16,8 +13,8 @@ private:
std::atomic<bool> stop = false;
std::condition_variable cv;
std::thread worker;
std::mutex mtx;
bool busy = false;
std::mutex mtx;
private:
void Runner();
public:
@@ -46,6 +43,5 @@ public:
/// Crete a thread which will immediately run a task and then wait for another.
explicit Thread(const std::function<void()>& task);
/// Waits for the current task to finish (if there is one) and destroys the thread.
// TODO Avoid spinning.
~Thread();
};

View File

@@ -3,6 +3,7 @@
#include <MultiThreading/Thread.h>
#include <vector>
#include <queue>
namespace MultiThreading {
class ThreadPool;
}

View File

@@ -47,7 +47,7 @@ int main() {
srand(time(nullptr));
int32_t task_completion_count = 0;
auto* thread_pool = new ThreadPool();
auto* thread_pool = new ThreadPool(4);
for (unsigned int i = 0; i < 128; i++) {
auto some_task = Task<int32_t>::Create(([i] { return some_test_func(i + 1); }), &task_completion_count);

View File

@@ -5,7 +5,7 @@ using namespace MultiThreading;
Thread::~Thread() {
if (current_task)
while (!current_task->Complete()) {}
current_task->WaitComplete();
stop = true;
cv.notify_one();