From a03a0aaa4e3067dc7adce4b1bc7d04f063ae92e7 Mon Sep 17 00:00:00 2001 From: Colin S Date: Fri, 20 Dec 2024 18:10:48 -0800 Subject: [PATCH] fixes #12 --- tests/CMakeLists.txt | 62 +++++++++++++--------- tests/Threadpool.cpp | 83 +++++++++++++++++++++++++++++ tests/Threadpool.hpp | 124 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 tests/Threadpool.cpp create mode 100644 tests/Threadpool.hpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 40eb383..492762a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,29 +1,39 @@ -add_executable( - unit - unit.cpp - ${SIGFN_SOURCES}) +# add_executable( +# unit +# unit.cpp +# ${SIGFN_SOURCES}) -target_include_directories(unit PRIVATE ${SIGFN_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/../src) +# target_include_directories(unit PRIVATE ${SIGFN_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/../src) -if(SIGFN_COVER) - if(WIN32) - message("skipping code coverage for windows") - else() - target_compile_options(unit PRIVATE -fprofile-arcs -ftest-coverage -g -O0) - target_link_libraries(unit PRIVATE gcov "--coverage") - add_custom_target( - cover - DEPENDS unit) - add_custom_command( - TARGET cover - COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR}/.. -e ${CMAKE_CURRENT_SOURCE_DIR}) - endif() -endif() +# if(SIGFN_COVER) +# if(WIN32) +# message("skipping code coverage for windows") +# else() +# target_compile_options(unit PRIVATE -fprofile-arcs -ftest-coverage -g -O0) +# target_link_libraries(unit PRIVATE gcov "--coverage") +# add_custom_target( +# cover +# DEPENDS unit) +# add_custom_command( +# TARGET cover +# COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR}/.. -e ${CMAKE_CURRENT_SOURCE_DIR}) +# endif() +# endif() -add_test(NAME sigfn_handle COMMAND unit sigfn_handle ) -add_test(NAME sigfn_ignore COMMAND unit sigfn_ignore) -add_test(NAME sigfn_reset COMMAND unit sigfn_reset) -add_test(NAME sigfn_error COMMAND unit sigfn_error) -add_test(NAME sigfn::handle COMMAND unit sigfn::handle) -add_test(NAME sigfn::ignore COMMAND unit sigfn::ignore) -add_test(NAME sigfn::reset COMMAND unit sigfn::reset) +# add_test(NAME sigfn_handle COMMAND unit sigfn_handle ) +# add_test(NAME sigfn_ignore COMMAND unit sigfn_ignore) +# add_test(NAME sigfn_reset COMMAND unit sigfn_reset) +# add_test(NAME sigfn_error COMMAND unit sigfn_error) +# add_test(NAME sigfn::handle COMMAND unit sigfn::handle) +# add_test(NAME sigfn::ignore COMMAND unit sigfn::ignore) +# add_test(NAME sigfn::reset COMMAND unit sigfn::reset) + +add_library(threadpool_a STATIC threadpool.cpp) +target_include_directories(threadpool_a PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(threadpool SHARED threadpool.cpp) +target_include_directories(threadpool PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(threadpool INTERFACE) + +target_include_directories(threadpool INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/tests/Threadpool.cpp b/tests/Threadpool.cpp new file mode 100644 index 0000000..c3fbe2e --- /dev/null +++ b/tests/Threadpool.cpp @@ -0,0 +1,83 @@ +#include +#include "threadpool.hpp" + +maxtek::threadpool::threadpool(size_t threads) +{ + if (threads == 0) + { + throw std::runtime_error("failed to construct threadpool with zero threads"); + } + + _active = true; + + _workers.reserve(threads); + + while (_workers.size() < _workers.capacity()) + { + _workers.push_back(std::thread([&]() + { + std::function task; + while (pop_task(task)) + { + task(); + } })); + } +} + +maxtek::threadpool::~threadpool() +{ + if (_active) + { + shutdown(); + } +} + +bool maxtek::threadpool::active() const +{ + return _active; +} + +void maxtek::threadpool::shutdown() +{ + if (!_active) + { + throw std::runtime_error("failed to shut down inactive threadpool"); + } + _active = false; + _condition.notify_all(); + for (std::thread &worker : _workers) + { + worker.join(); + } +} + +void maxtek::threadpool::push_task(std::function &&task) +{ + std::unique_lock lock(_mutex); + if (!_active) + { + throw std::runtime_error("failed to submit to inactive threadpool"); + } + _tasks.push(std::move(task)); + lock.unlock(); + _condition.notify_one(); +} + +bool maxtek::threadpool::pop_task(std::function &task) +{ + std::unique_lock lock(_mutex); + bool result(false); + _condition.wait( + lock, + [&]() + { + return (!_active || !_tasks.empty()); + }); + if (_active) + { + task = _tasks.front(); + _tasks.pop(); + result = true; + } + return result; +} \ No newline at end of file diff --git a/tests/Threadpool.hpp b/tests/Threadpool.hpp new file mode 100644 index 0000000..a6fafd2 --- /dev/null +++ b/tests/Threadpool.hpp @@ -0,0 +1,124 @@ +/* +** Copyright 2024 Maxtek Consulting +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in all +** copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. +*/ + +#ifndef THREADPOOL_HPP +#define THREADPOOL_HPP + +/** + * @file threadpool.hpp + * @brief Maxtek threadpool + * @author Max Guerrero and John R. Patek Sr. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define DLL_EXPORT __declspec(dllexport) +#else +#define DLL_EXPORT +#endif + +namespace maxtek +{ + /** + * @brief threadpool + * + * @class allows tasks to be submitted and executed asynchronously across multiple threads. + */ + class DLL_EXPORT threadpool + { + public: + + /** + * @brief constructs a new threadpool + * + * @param threads number of threads to use for constructing the threadpool + * @exception std::runtime_error if threads is set to zero + */ + threadpool(size_t threads = std::thread::hardware_concurrency()); + + /** + * @brief destroys threadpool after calling shutdown if necessary + */ + ~threadpool(); + + /** + * @brief submits a function with its arguments to the threadpool + * @tparam F function signature + * @tparam Args function argument types + * @param function function signature + * @param args arguments to pass to the function + * @returns a future holding the asynchronous function result + * @exception std::runtime_error if the thread pool has been shut down + */ + template + std::future> submit(F &&function, Args &&...args) + { + std::shared_ptr()>> packaged_task; + std::function work; + std::future> result; + + packaged_task = std::make_shared()>>(std::bind(std::forward(function), std::forward(args)...)); + result = packaged_task->get_future(); + + work = [packaged_task]() + { + (*packaged_task)(); + }; + + push_task(std::move(work)); + + return result; + } + + /** + * @brief check if the threadpool is active + * @returns true if the threadpool is active, false if it has been shut down + */ + bool active() const; + + /** + * @brief shut down threadpool by joining threads and rejecting submissions + * @exception std::runtime_error if the thread pool has already been shut down + */ + void shutdown(); + + private: + void push_task(std::function &&task); + bool pop_task(std::function &task); + + size_t num_threads; + bool _active; + std::vector _workers; + std::queue> _tasks; + std::mutex _mutex; + std::condition_variable _condition; + }; +} +#endif \ No newline at end of file