diff --git a/src/util/thread_pool_executor.cc b/src/util/thread_pool_executor.cc new file mode 100644 index 00000000..b496d2e9 --- /dev/null +++ b/src/util/thread_pool_executor.cc @@ -0,0 +1,23 @@ +/** +* src/util/thread_pool_executor.cc +* +* Copyright (c) 2021-2022 Bartek Kryza +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "thread_pool_executor.h" + +namespace clanguml::util { + +} diff --git a/src/util/thread_pool_executor.h b/src/util/thread_pool_executor.h new file mode 100644 index 00000000..4a570c72 --- /dev/null +++ b/src/util/thread_pool_executor.h @@ -0,0 +1,109 @@ +/** + * src/util/thread_pool_executor.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +namespace clanguml::util { +class thread_pool_executor { +public: + thread_pool_executor() + : thread_pool_executor{0} + { + } + + thread_pool_executor(unsigned int pool_size) + : done_{false} + { + if (pool_size == 0U) + pool_size = std::thread::hardware_concurrency(); + + for (auto i = 0U; i < pool_size; i++) { + threads_.push_back( + std::thread{&thread_pool_executor::worker, this}); + } + } + + ~thread_pool_executor() { stop(); } + + std::future add(std::function &&task) + { + std::unique_lock l(tasks_mutex_); + + std::packaged_task ptask{std::move(task)}; + auto res = ptask.get_future(); + + tasks_.emplace_back(std::move(ptask)); + + l.unlock(); + tasks_cond_.notify_one(); + + return res; + } + + void stop() + { + done_ = true; + for (auto &thread : threads_) { + if (thread.joinable()) + thread.join(); + } + } + +private: + void worker() + { + try { + while (!done_) { + auto task = get(); + + task(); + } + } + catch (std::runtime_error &e) { + } + } + + std::packaged_task get() + { + std::unique_lock l(tasks_mutex_); + + while (tasks_.empty()) { + if (done_) + throw std::runtime_error("Thread pool closing..."); + + tasks_cond_.wait_for(l, std::chrono::seconds(1)); + } + + auto res = std::move(tasks_.front()); + tasks_.pop_front(); + return res; + } + + std::atomic_bool done_; + + std::deque> tasks_; + std::mutex tasks_mutex_; + std::condition_variable tasks_cond_; + + std::vector threads_; +}; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b825fc09..2c4f6eed 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,6 +48,13 @@ set(CLANG_UML_TEST_CONFIG_HEADER catch.h ) +set(CLANG_UML_TEST_THREAD_POOL_EXECUTOR_SRC + test_thread_pool_executor.cc + ) +set(CLANG_UML_TEST_THREAD_POOL_EXECUTOR_HEADER + catch.h + ) + add_executable(test_util ${CLANG_UML_TEST_UTIL_SRC} ${CLANG_UML_TEST_UTIL_HEADER}) @@ -88,6 +95,16 @@ target_link_libraries(test_config ${YAML_CPP_LIBRARIES} spdlog::spdlog clang-umllib cppast) +add_executable(test_thread_pool_executor + ${CLANG_UML_TEST_THREAD_POOL_EXECUTOR_SRC} + ${CLANG_UML_TEST_THREAD_POOL_EXECUTOR_HEADER}) + +target_link_libraries(test_thread_pool_executor + PRIVATE + ${LIBCLANG_LIBRARIES} + ${YAML_CPP_LIBRARIES} + spdlog::spdlog clang-umllib cppast) + add_executable(test_cases ${CLANG_UML_TEST_CASES_SRC} ${CLANG_UML_TEST_CASES_HEADER}) diff --git a/tests/test_thread_pool_executor.cc b/tests/test_thread_pool_executor.cc new file mode 100644 index 00000000..e4a209e3 --- /dev/null +++ b/tests/test_thread_pool_executor.cc @@ -0,0 +1,45 @@ +/** + * tests/test_thread_pool_executor.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define CATCH_CONFIG_MAIN + +#include "catch.h" + +#include "util/thread_pool_executor.h" + +TEST_CASE("Test thread_pool_executor", "[unit-test]") +{ + using clanguml::util::thread_pool_executor; + + thread_pool_executor pool{4}; + + std::atomic_int counter{0}; + + std::vector> futs; + + const unsigned int kTaskCount = 1000; + + for (auto i = 0U; i < kTaskCount; i++) { + futs.emplace_back(pool.add([&counter]() { counter++; })); + } + + for (auto &f : futs) { + f.get(); + } + + CHECK(counter == kTaskCount); +}