├── .github └── workflows │ └── thread_pool.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Config.cmake.in ├── LICENSE ├── README.md ├── include └── thread_pool │ └── thread_pool.hpp └── test └── thread_pool_test.cpp /.github/workflows/thread_pool.yml: -------------------------------------------------------------------------------- 1 | name: thread_pool CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | compiler: 17 | - g++ 18 | - g++-4.8 19 | - clang++ 20 | - clang++-4.0 21 | 22 | runs-on: ubuntu-18.04 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - if: ${{ matrix.compiler == 'g++-4.8' }} 28 | name: Setup GCC 29 | uses: egor-tensin/setup-gcc@v1 30 | with: 31 | version: "4.8" 32 | platform: x64 33 | 34 | - if: ${{ matrix.compiler == 'clang++-4.0' }} 35 | name: Setup Clang 36 | uses: egor-tensin/setup-clang@v1 37 | with: 38 | version: "4.0" 39 | platform: x64 40 | 41 | - name: Configure CMake 42 | run: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} 43 | env: 44 | CXX: ${{ matrix.compiler }} 45 | 46 | - name: Build 47 | run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} 48 | 49 | - name: Test 50 | working-directory: ${{ github.workspace }}/build 51 | run: bin/thread_pool_test 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | build/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | project(thread_pool VERSION 4.0.1 4 | LANGUAGES CXX 5 | DESCRIPTION "ThreadPool is a c++ header only library combining https://github.com/progschj/ThreadPool and task stealing by Sean Parent.") 6 | 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") 8 | set(CMAKE_CXX_STANDARD 11) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | set(CMAKE_CXX_EXTENSIONS OFF) 11 | set(THREADS_PREFER_PTHREAD_FLAG ON) 12 | 13 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 14 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 15 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 16 | 17 | if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 18 | set(thread_pool_main_project ON) 19 | endif () 20 | option(thread_pool_install "Generate install target" ${thread_pool_main_project}) 21 | option(thread_pool_build_tests "Build unit tests" ${thread_pool_main_project}) 22 | 23 | find_package(Threads REQUIRED) 24 | 25 | if (thread_pool_build_tests) 26 | find_package(GTest 1.10.0 QUIET) 27 | if (NOT GTest_FOUND) 28 | include(FetchContent) 29 | 30 | FetchContent_Declare( 31 | googletest 32 | GIT_REPOSITORY https://github.com/google/googletest 33 | GIT_TAG release-1.10.0) 34 | 35 | FetchContent_GetProperties(googletest) 36 | if (NOT googletest_POPULATED) 37 | FetchContent_Populate(googletest) 38 | add_subdirectory( 39 | ${googletest_SOURCE_DIR} 40 | ${googletest_BINARY_DIR} 41 | EXCLUDE_FROM_ALL) 42 | add_library(GTest::Main ALIAS gtest_main) 43 | endif () 44 | endif () 45 | endif () 46 | 47 | add_library(thread_pool INTERFACE) 48 | add_library(${PROJECT_NAME}::thread_pool ALIAS thread_pool) 49 | 50 | target_include_directories(thread_pool INTERFACE 51 | $ 52 | $) 53 | 54 | target_link_libraries(thread_pool INTERFACE 55 | Threads::Threads) 56 | 57 | if (thread_pool_install) 58 | include(GNUInstallDirs) 59 | include(CMakePackageConfigHelpers) 60 | 61 | configure_package_config_file( 62 | ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in 63 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 64 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) 65 | write_basic_package_version_file( 66 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 67 | COMPATIBILITY SameMajorVersion) 68 | 69 | install( 70 | TARGETS thread_pool 71 | EXPORT ${PROJECT_NAME}Targets) 72 | install( 73 | DIRECTORY include/thread_pool 74 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 75 | install( 76 | EXPORT ${PROJECT_NAME}Targets 77 | NAMESPACE ${PROJECT_NAME}:: 78 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) 79 | install( 80 | FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 81 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 82 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) 83 | endif () 84 | 85 | if (thread_pool_build_tests) 86 | add_executable(thread_pool_test 87 | test/thread_pool_test.cpp) 88 | 89 | target_link_libraries(thread_pool_test 90 | thread_pool 91 | GTest::Main) 92 | endif () 93 | -------------------------------------------------------------------------------- /Config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | find_dependency(Threads) 5 | 6 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 7 | check_required_components("@PROJECT_NAME@") 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Robert Vaser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thread pool 2 | 3 | [![Latest GitHub release](https://img.shields.io/github/release/rvaser/thread_pool.svg)](https://github.com/rvaser/thread_pool/releases/latest) 4 | ![Build status for gcc/clang](https://github.com/rvaser/thread_pool/actions/workflows/thread_pool.yml/badge.svg) 5 | 6 | ThreadPool is a c++ header only library combining https://github.com/progschj/ThreadPool and task stealing by Sean Parent. 7 | 8 | ## Usage 9 | 10 | To build thread_pool run the following commands: 11 | ```bash 12 | git clone https://github.com/rvaser/thread_pool && cd thread_pool && mkdir build && cd build 13 | cmake -DCMAKE_BUILD_TYPE=Release .. && make 14 | ``` 15 | which will create install targets and unit tests. Running `make install` will create a package on your system that can be searched and linked with: 16 | ```cmake 17 | find_package(thread_pool) 18 | target_link_libraries( thread_pool:thread_pool) 19 | ``` 20 | On the other hand, you can include thread_pool as a submodule and add it to your project with the following: 21 | ```cmake 22 | if (NOT TARGET thread_pool) 23 | add_subdirectory(/thread_pool EXCLUDE_FROM_ALL) 24 | endif () 25 | target_link_libraries( thread_pool::thread_pool) 26 | ``` 27 | 28 | If you are not using CMake, include the appropriate header file directly to your project and link with pthread. 29 | 30 | #### Build options 31 | 32 | - `thread_pool_install`: generate install target 33 | - `thread_pool_build_tests`: build unit tests 34 | 35 | #### Dependencies 36 | 37 | - gcc 4.8+ | clang 3.5+ 38 | - pthread 39 | - (optional) cmake 3.11+ 40 | 41 | ###### Hidden 42 | 43 | - (thread_pool_test) google/googletest 1.10.0 44 | 45 | ## Examples 46 | 47 | ```cpp 48 | #include "thread_pool/thread_pool.hpp" 49 | 50 | int function1(const T& t, ...) { 51 | ... 52 | } 53 | int function2(...) { 54 | ... 55 | } 56 | ... 57 | auto lambda1 = [...] (...) -> void { 58 | ... 59 | }; 60 | 61 | thread_pool::ThreadPool thread_pool{}; 62 | 63 | std::vector> futures; 64 | for (...) { 65 | // be sure to used std::ref() or std::cref() for references 66 | futures.emplace_back(thread_pool.Submit(function1, std::cref(t), ...)); 67 | futures.emplace_back(thread_pool.Submit(function2, ...)); 68 | } 69 | for (auto& it : futures) { 70 | ... = it.get(); 71 | } 72 | 73 | std::vector> void_futures; 74 | for (...) { 75 | void_futures.emplace_back(thread_pool.Submit(lambda1, ...)); 76 | } 77 | for (const auto& it : void_futures) { 78 | it.wait(); 79 | } 80 | ``` 81 | 82 | ## Acknowledgement 83 | 84 | This work has been supported in part by the Croatian Science Foundation under the project Single genome and metagenome assembly (IP-2018-01-5886). 85 | -------------------------------------------------------------------------------- /include/thread_pool/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Robert Vaser 2 | // Combination of ThreadPool implementation by progschj and 3 | // task stealing by Sean Parent 4 | 5 | #ifndef THREAD_POOL_THREAD_POOL_HPP_ 6 | #define THREAD_POOL_THREAD_POOL_HPP_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include // NOLINT 12 | #include 13 | #include 14 | #include // NOLINT 15 | #include 16 | #include 17 | #include 18 | 19 | namespace thread_pool { 20 | 21 | class ThreadPool { 22 | public: 23 | explicit ThreadPool( 24 | std::size_t num_threads = std::thread::hardware_concurrency()) 25 | : threads_(), 26 | thread_map_(), 27 | queues_(std::max(1UL, num_threads)), 28 | task_id_(0) { 29 | for (std::size_t i = 0; i != queues_.size(); ++i) { 30 | threads_.emplace_back([this, i] () -> void { Task(i); }); 31 | thread_map_.emplace(threads_.back().get_id(), i); 32 | } 33 | } 34 | 35 | ThreadPool(const ThreadPool&) = delete; 36 | ThreadPool& operator=(const ThreadPool&) = delete; 37 | 38 | ThreadPool(ThreadPool&&) = delete; 39 | ThreadPool& operator=(ThreadPool&&) = delete; 40 | 41 | ~ThreadPool() { 42 | for (auto& it : queues_) { 43 | it.Done(); 44 | } 45 | for (auto& it : threads_) { 46 | it.join(); 47 | } 48 | } 49 | 50 | std::size_t num_threads() const { 51 | return threads_.size(); 52 | } 53 | 54 | const std::unordered_map& thread_map() const { 55 | return thread_map_; 56 | } 57 | 58 | template 59 | auto Submit(T&& routine, Ts&&... params) 60 | -> std::future::type> { 61 | auto task = std::make_shared::type()>>( // NOLINT 62 | std::bind(std::forward(routine), std::forward(params)...)); 63 | auto task_result = task->get_future(); 64 | auto task_wrapper = [task] () { 65 | (*task)(); 66 | }; 67 | 68 | auto task_id = task_id_++; 69 | bool is_submitted = false; 70 | for (std::size_t i = 0; i != queues_.size() * 42; ++i) { 71 | if (queues_[(task_id + i) % queues_.size()].TryPush(task_wrapper)) { 72 | is_submitted = true; 73 | break; 74 | } 75 | } 76 | if (!is_submitted) { 77 | queues_[task_id % queues_.size()].Push(task_wrapper); 78 | } 79 | 80 | return task_result; 81 | } 82 | 83 | private: 84 | void Task(std::size_t thread_id) { 85 | while (true) { 86 | std::function task; 87 | 88 | for (std::size_t i = 0; i != queues_.size(); ++i) { 89 | if (queues_[(thread_id + i) % queues_.size()].TryPop(&task)) { 90 | break; 91 | } 92 | } 93 | if (!task && !queues_[thread_id].Pop(&task)) { 94 | break; 95 | } 96 | 97 | task(); 98 | } 99 | } 100 | 101 | struct TaskQueue { 102 | public: 103 | template 104 | void Push(F&& f) { 105 | { 106 | std::unique_lock lock(mutex); 107 | queue.emplace(std::forward(f)); 108 | } 109 | is_ready.notify_one(); 110 | } 111 | 112 | bool Pop(std::function* f) { 113 | std::unique_lock lock(mutex); 114 | while (queue.empty() && !is_done) { 115 | is_ready.wait(lock); 116 | } 117 | if (queue.empty()) { 118 | return false; 119 | } 120 | *f = std::move(queue.front()); 121 | queue.pop(); 122 | return true; 123 | } 124 | 125 | template 126 | bool TryPush(F&& f) { 127 | { 128 | std::unique_lock lock(mutex, std::try_to_lock); 129 | if (!lock) { 130 | return false; 131 | } 132 | queue.emplace(std::forward(f)); 133 | } 134 | is_ready.notify_one(); 135 | return true; 136 | } 137 | 138 | bool TryPop(std::function* f) { 139 | std::unique_lock lock(mutex, std::try_to_lock); 140 | if (!lock || queue.empty()) { 141 | return false; 142 | } 143 | *f = std::move(queue.front()); 144 | queue.pop(); 145 | return true; 146 | } 147 | 148 | void Done() { 149 | { 150 | std::unique_lock lock(mutex); 151 | is_done = true; 152 | } 153 | is_ready.notify_all(); 154 | } 155 | 156 | std::queue> queue; 157 | std::mutex mutex; 158 | std::condition_variable is_ready; 159 | bool is_done = false; 160 | }; 161 | 162 | std::vector threads_; 163 | std::unordered_map thread_map_; 164 | std::vector queues_; 165 | std::atomic task_id_; 166 | }; 167 | 168 | } // namespace thread_pool 169 | 170 | #endif // THREAD_POOL_THREAD_POOL_HPP_ 171 | -------------------------------------------------------------------------------- /test/thread_pool_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Robert Vaser 2 | 3 | #include "thread_pool/thread_pool.hpp" 4 | 5 | #include "gtest/gtest.h" 6 | 7 | namespace thread_pool { 8 | namespace test { 9 | 10 | TEST(ThreadPoolThreadPoolTest, Submit) { 11 | std::function fibonacci = 12 | [&fibonacci] (std::size_t i) -> std::size_t { 13 | if (i == 1 || i == 2) { 14 | return 1; 15 | } 16 | return fibonacci(i - 1) + fibonacci(i - 2); 17 | }; 18 | 19 | ThreadPool tp{}; 20 | 21 | std::vector> f; 22 | for (std::size_t i = 0; i < tp.num_threads(); ++i) { 23 | f.emplace_back(tp.Submit(fibonacci, 42)); 24 | } 25 | for (auto& it : f) { 26 | EXPECT_EQ(267914296, it.get()); 27 | } 28 | } 29 | 30 | TEST(ThreadPoolThreadPoolTest, ThreadIds) { 31 | ThreadPool tp{}; 32 | EXPECT_EQ(tp.num_threads(), tp.thread_map().size()); 33 | 34 | auto thread_id = [&] () -> std::size_t { 35 | return tp.thread_map().count(std::this_thread::get_id()); 36 | }; 37 | 38 | std::vector> f; 39 | for (std::size_t i = 0; i < tp.num_threads() * 42; ++i) { 40 | f.emplace_back(tp.Submit(thread_id)); 41 | } 42 | for (auto& it : f) { 43 | EXPECT_EQ(1, it.get()); 44 | } 45 | } 46 | 47 | } // namespace test 48 | } // namespace thread_pool 49 | --------------------------------------------------------------------------------