├── .clang-format ├── .clang-tidy ├── .gitignore ├── .gitlab-ci.yml ├── CMakeLists.txt ├── COPYING ├── README.md ├── ThreadPool.cpp ├── ThreadPool.hpp ├── example.cpp ├── meson.build └── mingw.ini /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | TabWidth: 4 4 | AccessModifierOffset: -4 5 | UseTab: ForIndentation 6 | AllowShortIfStatementsOnASingleLine: true 7 | ColumnLimit: 100 8 | ConstructorInitializerIndentWidth: 0 9 | AllowShortFunctionsOnASingleLine: None 10 | PointerAlignment: Left 11 | SortIncludes: false 12 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 13 | SpaceBeforeParens: ControlStatements 14 | SpacesInContainerLiterals: true 15 | Cpp11BracedListStyle: false 16 | IndentCaseLabels: true 17 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-cert-err58-cpp,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-readability-todo,-google-runtime-references,-readability-named-parameter,-clang-diagnostic-unused-command-line-argument,-readability-implicit-bool-cast,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-fuchsia-default-arguments' 3 | WarningsAsErrors: 'hicpp-use-override,modernize-avoid-bind,llvm-namespace-comment,modernize-use-nullptr' 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build*/ 2 | /.vscode/ 3 | /compile_commands.json 4 | /.cache/ 5 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | gcc: 2 | image: fedora:28 3 | stage: build 4 | script: 5 | - dnf install -y meson gcc-c++ 6 | - meson build 7 | - ninja -C build test install 8 | 9 | clang: 10 | image: fedora:28 11 | stage: build 12 | script: 13 | - dnf install -y meson clang 14 | - CC=clang CXX=clang++ meson build 15 | - ninja -C build test 16 | 17 | mingw: 18 | image: fedora:28 19 | stage: build 20 | script: 21 | - dnf install -y gcc-c++ meson mingw64-gcc-c++ wine-core 22 | - meson build --cross-file mingw.ini 23 | - WINEPATH=/usr/x86_64-w64-mingw32/sys-root/mingw/bin ninja -C build test 24 | 25 | variables: 26 | GIT_SUBMODULE_STRATEGY: recursive 27 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(ThreadPool) 3 | 4 | include(GNUInstallDirs) 5 | 6 | set(CMAKE_INSTALL_CMAKEDIR "share/cmake" CACHE STRING "Install location for cmake related files") 7 | mark_as_advanced(CMAKE_INSTALL_CMAKEDIR) 8 | 9 | set(LIBRARY_TYPES "OBJECT;STATIC;SHARED") 10 | set(THREADPOOL_LIBRARY_TYPE "OBJECT" CACHE STRING "Choose the type of library to build, options are: ${LIBRARY_TYPES}.") 11 | set_property(CACHE THREADPOOL_LIBRARY_TYPE PROPERTY STRINGS ${LIBRARY_TYPES}) 12 | if (NOT THREADPOOL_LIBRARY_TYPE IN_LIST LIBRARY_TYPES) 13 | message(FATAL_ERROR "Invalid option: THREADPOOL_LIBRARY_TYPE=${THREADPOOL_LIBRARY_TYPE}. Supported options: ${LIBRARY_TYPES}.") 14 | endif() 15 | 16 | find_package(Threads REQUIRED) 17 | 18 | add_library(ThreadPool ${THREADPOOL_LIBRARY_TYPE} ThreadPool.cpp) 19 | target_link_libraries(ThreadPool PUBLIC Threads::Threads) 20 | 21 | target_include_directories(ThreadPool 22 | PUBLIC $ 23 | $) 24 | 25 | target_compile_features(ThreadPool PRIVATE cxx_std_17) 26 | 27 | # Define public headers to get them automatically installed. 28 | set_target_properties(ThreadPool PROPERTIES 29 | PUBLIC_HEADER "ThreadPool.hpp") 30 | 31 | set(THREADPOOL_EXPORT ThreadPoolConfig) 32 | 33 | # Install the lib and its headers. Flag it for export. 34 | install( 35 | TARGETS ThreadPool 36 | EXPORT ${THREADPOOL_EXPORT} 37 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 38 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 39 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 40 | ) 41 | 42 | # Create the export file for the build tree. 43 | export(TARGETS ThreadPool FILE "${PROJECT_BINARY_DIR}/${THREADPOOL_EXPORT}.cmake") 44 | 45 | # Create the export file for the install tree. 46 | install(EXPORT ${THREADPOOL_EXPORT} 47 | DESTINATION "${CMAKE_INSTALL_CMAKEDIR}") 48 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Jakob Progsch, Václav Zeman 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source 20 | distribution. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThreadPool [![pipeline status](https://gitlab.com/jhasse/ThreadPool/badges/master/pipeline.svg)](https://gitlab.com/jhasse/ThreadPool/commits/master) 2 | 3 | A simple C++17 Thread Pool implementation. 4 | 5 | ## Basic usage 6 | 7 | ```c++ 8 | // create thread pool with 4 worker threads 9 | ThreadPool pool(4); 10 | 11 | // enqueue and store future 12 | auto result = pool.enqueue([](int answer) { return answer; }, 42); 13 | 14 | // get result from future 15 | std::cout << result.get() << std::endl; 16 | 17 | ``` 18 | 19 | ## CMake 20 | 21 | ### Build options 22 | 23 | - `THREADPOOL_LIBRARY_TYPE`: Set the type of library to build. 24 | 25 | Supported options: `OBJECT` (default), `STATIC`, `SHARED`. 26 | 27 | ### Import using `find_package` 28 | 29 | Build and install ThreadPool somewhere on your system (note: `OBJECT` libraries cannot be installed). 30 | 31 | ```cmake 32 | find_package(Threads REQUIRED) 33 | 34 | set(ThreadPool_DIR "/share/cmake") 35 | # You can actually skip the install step and use the build directory. 36 | set(ThreadPool_DIR "") 37 | 38 | find_package(Threads REQUIRED) 39 | find_package(ThreadPool) 40 | 41 | target_link_libraries( PRIVATE ThreadPool) 42 | ``` 43 | 44 | ### Import using `FetchContent` (CMake >= 3.11) 45 | 46 | ```cmake 47 | find_package(Threads REQUIRED) 48 | 49 | FetchContent_Declare(ThreadPool 50 | GIT_REPOSITORY git://github.com/jhasse/ThreadPool.git 51 | ) 52 | 53 | # Optional 54 | # set(THREADPOOL_LIBRARY_TYPE SHARED) 55 | 56 | # If CMake >= 3.14 57 | FetchContent_MakeAvailable(ThreadPool) 58 | 59 | # If CMake < 3.14 60 | FetchContent_GetProperties(ThreadPool) 61 | if (NOT ThreadPool_POPULATED) 62 | FetchContent_Populate(ThreadPool) 63 | add_subdirectory("${ThreadPool_SOURCE_DIR}" ${ThreadPool_BINARY_DIR}) 64 | endif() 65 | 66 | target_link_libraries( PRIVATE ThreadPool) 67 | ``` -------------------------------------------------------------------------------- /ThreadPool.cpp: -------------------------------------------------------------------------------- 1 | #include "ThreadPool.hpp" 2 | 3 | // the destructor joins all threads 4 | ThreadPool::~ThreadPool() { 5 | { 6 | std::unique_lock lock(queue_mutex); 7 | condition_producers.wait(lock, [this] { return tasks.empty(); }); 8 | stop = true; 9 | } 10 | condition.notify_all(); 11 | for (std::thread& worker : workers) { 12 | worker.join(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ThreadPool.hpp: -------------------------------------------------------------------------------- 1 | #ifndef THREAD_POOL_HPP 2 | #define THREAD_POOL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class ThreadPool { 9 | public: 10 | explicit ThreadPool(size_t); 11 | template 12 | decltype(auto) enqueue(F&& f, Args&&... args); 13 | ~ThreadPool(); 14 | private: 15 | // need to keep track of threads so we can join them 16 | std::vector< std::thread > workers; 17 | // the task queue 18 | std::queue< std::packaged_task > tasks; 19 | 20 | // synchronization 21 | std::mutex queue_mutex; 22 | std::condition_variable condition; 23 | std::condition_variable condition_producers; 24 | bool stop; 25 | }; 26 | 27 | // the constructor just launches some amount of workers 28 | inline ThreadPool::ThreadPool(size_t threads) 29 | : stop(false) 30 | { 31 | for(size_t i = 0;i task; 38 | 39 | { 40 | std::unique_lock lock(this->queue_mutex); 41 | this->condition.wait(lock, 42 | [this]{ return this->stop || !this->tasks.empty(); }); 43 | if(this->stop && this->tasks.empty()) 44 | return; 45 | task = std::move(this->tasks.front()); 46 | this->tasks.pop(); 47 | if (tasks.empty()) { 48 | condition_producers.notify_one(); // notify the destructor that the queue is empty 49 | } 50 | } 51 | 52 | task(); 53 | } 54 | } 55 | ); 56 | } 57 | 58 | // add new work item to the pool 59 | template 60 | decltype(auto) ThreadPool::enqueue(F&& f, Args&&... args) 61 | { 62 | using return_type = std::invoke_result_t; 63 | 64 | std::packaged_task task( 65 | std::bind(std::forward(f), std::forward(args)...) 66 | ); 67 | 68 | std::future res = task.get_future(); 69 | { 70 | std::unique_lock lock(queue_mutex); 71 | 72 | // don't allow enqueueing after stopping the pool 73 | if(stop) 74 | throw std::runtime_error("enqueue on stopped ThreadPool"); 75 | 76 | tasks.emplace(std::move(task)); 77 | } 78 | condition.notify_one(); 79 | return res; 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ThreadPool.hpp" 7 | 8 | int main() 9 | { 10 | 11 | ThreadPool pool(4); 12 | std::vector< std::future > results; 13 | 14 | for(int i = 0; i < 8; ++i) { 15 | results.emplace_back( 16 | pool.enqueue([i] { 17 | std::cout << "hello " << i << std::endl; 18 | std::this_thread::sleep_for(std::chrono::seconds(1)); 19 | std::cout << "world " << i << std::endl; 20 | return i*i; 21 | }) 22 | ); 23 | } 24 | 25 | for(auto && result: results) 26 | std::cout << result.get() << ' '; 27 | std::cout << std::endl; 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('ThreadPool', ['cpp'], default_options : ['cpp_std=c++17']) 2 | 3 | threads_dep = dependency('threads') 4 | 5 | lib = static_library('ThreadPool', 'ThreadPool.cpp') 6 | 7 | test('Example', executable('example', sources : ['example.cpp'], dependencies : [threads_dep], 8 | link_with : lib)) 9 | 10 | install_headers('ThreadPool.hpp') 11 | 12 | inc = include_directories('.') 13 | threadpool_dep = declare_dependency(include_directories : inc, link_with : lib) 14 | -------------------------------------------------------------------------------- /mingw.ini: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'x86_64-w64-mingw32-gcc' 3 | cpp = 'x86_64-w64-mingw32-g++' 4 | ar = 'x86_64-w64-mingw32-ar' 5 | strip = 'x86_64-w64-mingw32-strip' 6 | windres = 'x86_64-w64-mingw32-windres' 7 | pkgconfig = 'mingw64-pkg-config' 8 | exe_wrapper = 'wine64' 9 | 10 | [host_machine] 11 | system = 'windows' 12 | cpu_family = 'x86' 13 | cpu = 'x86_64' 14 | endian = 'little' 15 | 16 | [target_machine] 17 | system = 'windows' 18 | cpu_family = 'x86' 19 | cpu = 'x86_64' 20 | endian = 'little' 21 | --------------------------------------------------------------------------------