From 6969cacacc63969e65f5ab53dad5c3ffe0147eaa Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Wed, 14 May 2025 00:53:24 +0200 Subject: [PATCH] feat: ThreadPool refac & dynamic thread management --- src/Game.cpp | 10 +++++--- src/ThreadPool.cpp | 51 ++++++++++++++++++++++++++++++++++++++++ src/ThreadPool.hpp | 57 +++++++++++---------------------------------- src/ThreadUtils.hpp | 10 ++++++++ 4 files changed, 82 insertions(+), 46 deletions(-) create mode 100644 src/ThreadPool.cpp diff --git a/src/Game.cpp b/src/Game.cpp index 4e7653b..ca78587 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -164,10 +164,14 @@ void Game::update() { } void Game::updateBallsParallel(float dt) { - const size_t chunkSize = - std::max(static_cast(1), - m_objects.size() / std::thread::hardware_concurrency()); + const size_t optimalThreads = + ThreadUtils::calculateOptimalThreads(m_objects.size()); + if (m_threadPool.getThreadCount() != optimalThreads) { + m_threadPool.resize(optimalThreads); + } + const size_t chunkSize = + std::max(static_cast(1), m_objects.size() / optimalThreads); std::vector> futures; for (size_t i = 0; i < m_objects.size(); i += chunkSize) { diff --git a/src/ThreadPool.cpp b/src/ThreadPool.cpp new file mode 100644 index 0000000..4e278df --- /dev/null +++ b/src/ThreadPool.cpp @@ -0,0 +1,51 @@ + +#include "ThreadPool.hpp" + +ThreadPool::ThreadPool(const size_t numThreads) : stop(false) { + startWorkers(numThreads); +} + +ThreadPool::~ThreadPool() { stopAllWorkers(); } + +void ThreadPool::resize(const size_t newSize) { + if (newSize == workers.size()) return; + + stopAllWorkers(); + workers.clear(); + stop = false; + startWorkers(newSize); +} + +void ThreadPool::stopAllWorkers() { + { + std::unique_lock lock(queueMutex); + stop = true; + } + condition.notify_all(); + for (auto& worker : workers) { + if (worker.joinable()) { + worker.join(); + } + } +} + +void ThreadPool::startWorkers(const size_t numWorkers) { + for (size_t i = 0; i < numWorkers; ++i) { + workers.emplace_back([this] { + while (true) { + std::function task; + { + std::unique_lock lock(this->queueMutex); + this->condition.wait( + lock, [this] { return this->stop || !this->tasks.empty(); }); + + if (this->stop && this->tasks.empty()) return; + + task = std::move(this->tasks.front()); + this->tasks.pop(); + } + task(); + } + }); + } +} diff --git a/src/ThreadPool.hpp b/src/ThreadPool.hpp index 0d62eb6..daa4e24 100644 --- a/src/ThreadPool.hpp +++ b/src/ThreadPool.hpp @@ -1,3 +1,4 @@ + #pragma once #include #include @@ -10,63 +11,33 @@ class ThreadPool { public: explicit ThreadPool(size_t numThreads); - ~ThreadPool(); [[nodiscard]] size_t getThreadCount() const { return workers.size(); } + void resize(size_t newSize); + template - auto enqueue(F &&f, Args &&...args) - -> std::future >; + auto enqueue(F&& f, Args&&... args) + -> std::future>; private: + void stopAllWorkers(); + void startWorkers(size_t numWorkers); + std::vector workers; - - std::queue > tasks; - + std::queue> tasks; std::mutex queueMutex; std::condition_variable condition; - bool stop; + bool stop{false}; }; -inline ThreadPool::ThreadPool(const size_t numThreads) : stop(false) { - for (size_t i = 0; i < numThreads; ++i) { - workers.emplace_back([this] { - while (true) { - std::function task; - { - std::unique_lock lock(this->queueMutex); - this->condition.wait( - lock, [this] { return this->stop || !this->tasks.empty(); }); - - if (this->stop && this->tasks.empty()) return; - - task = std::move(this->tasks.front()); - this->tasks.pop(); - } - - task(); - } - }); - } -} - -inline ThreadPool::~ThreadPool() { - { - std::unique_lock lock(queueMutex); - stop = true; - } - condition.notify_all(); - - for (auto &worker : workers) worker.join(); -} - template -auto ThreadPool::enqueue(F &&f, Args &&...args) - -> std::future > { +auto ThreadPool::enqueue(F&& f, Args&&... args) + -> std::future> { using return_type = std::invoke_result_t; - auto task = std::make_shared >( + auto task = std::make_shared>( std::bind(std::forward(f), std::forward(args)...)); std::future res = task->get_future(); @@ -79,4 +50,4 @@ auto ThreadPool::enqueue(F &&f, Args &&...args) condition.notify_one(); return res; -} +} \ No newline at end of file diff --git a/src/ThreadUtils.hpp b/src/ThreadUtils.hpp index cc1722e..1ae5ad4 100644 --- a/src/ThreadUtils.hpp +++ b/src/ThreadUtils.hpp @@ -1,3 +1,4 @@ +#pragma once #include namespace ThreadUtils { @@ -5,4 +6,13 @@ inline size_t calculateSafeWorkerThreads(const size_t reserve = 2) { const size_t total = std::thread::hardware_concurrency(); return total > reserve ? total - reserve : 1; } + +inline size_t calculateOptimalThreads(const size_t objectCount, + const size_t minObjectsPerThread = 100) { + const size_t maxThreads = calculateSafeWorkerThreads(); + const size_t optimalThreads = + std::max(static_cast(1), objectCount / minObjectsPerThread); + return std::min(maxThreads, optimalThreads); +} + } // namespace ThreadUtils