├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── _config.yml ├── benchmark ├── CMakeLists.txt ├── asio_thread_pool.hpp └── benchmark.cpp ├── cmake └── Findthread-pool-cpp.cmake ├── include ├── thread_pool.hpp └── thread_pool │ ├── fixed_function.hpp │ ├── mpmc_bounded_queue.hpp │ ├── thread_pool.hpp │ ├── thread_pool_options.hpp │ └── worker.hpp └── tests ├── CMakeLists.txt ├── fixed_function.t.cpp ├── thread_pool.t.cpp └── thread_pool_options.t.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "googletest"] 2 | path = googletest 3 | url = https://github.com/google/googletest.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | 4 | language: cpp 5 | 6 | os: linux 7 | 8 | compiler: g++ 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - lcov 14 | 15 | script: 16 | - mkdir build 17 | - cd build 18 | - cmake -DCMAKE_BUILD_TYPE=Release .. 19 | - make VERBOSE=1 20 | - ctest --verbose 21 | - cd .. 22 | 23 | after_success: 24 | - mkdir coverage 25 | - cd coverage 26 | - cmake -DCMAKE_BUILD_TYPE=Debug -DCOVERAGE=yes .. 27 | - make 28 | - ctest 29 | - lcov --directory . --capture --output-file coverage.info 30 | - lcov --remove coverage.info '/usr/*' --output-file coverage.info 31 | - lcov --remove coverage.info '*googletest*' --output-file coverage.info 32 | - lcov --remove coverage.info '*.t.cpp' --output-file coverage.info 33 | - lcov --list coverage.info 34 | - bash <(curl -s https://codecov.io/bash) -f coverage.info 35 | - cd .. 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(thread-pool-cpp CXX) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra") 6 | if(COVERAGE) 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g --coverage") 8 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 9 | endif() 10 | 11 | # gtest 12 | enable_testing() 13 | add_subdirectory(googletest) 14 | 15 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest/include) 16 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 17 | 18 | # Tests 19 | add_subdirectory(tests) 20 | 21 | # Benchmark 22 | add_subdirectory(benchmark) 23 | 24 | # Install 25 | file(GLOB_RECURSE INSTALL_FILES_LIST "${CMAKE_CURRENT_SOURCE_DIR}/include/*") 26 | set_source_files_properties(${INSTALL_FILES_LIST} PROPERTIES HEADER_FILE_ONLY 1) 27 | add_library(HEADER_ONLY_TARGET STATIC ${INSTALL_FILES_LIST}) 28 | set_target_properties(HEADER_ONLY_TARGET PROPERTIES LINKER_LANGUAGE CXX) 29 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION "include") 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrey Kubarkov 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-cpp 2 | ================= 3 | [![Build Status](https://travis-ci.org/inkooboo/thread-pool-cpp.svg?branch=master)](https://travis-ci.org/inkooboo/thread-pool-cpp) 4 | [![Codecov branch](https://img.shields.io/codecov/c/github/inkooboo/thread-pool-cpp/master.svg)](https://codecov.io/gh/inkooboo/thread-pool-cpp) 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 6 | 7 | * It is highly scalable and fast. 8 | * It is header only. 9 | * No external dependencies, only standard library needed. 10 | * It implements both work-stealing and work-distribution balancing startegies. 11 | * It implements cooperative scheduling strategy. 12 | 13 | Example run: 14 | Post job to thread pool is much faster than for boost::asio based thread pool. 15 | 16 | Benchmark job reposting 17 | ***thread pool cpp*** 18 | reposted 1000001 in 61.6754 ms 19 | reposted 1000001 in 62.0187 ms 20 | reposted 1000001 in 62.8785 ms 21 | reposted 1000001 in 70.2714 ms 22 | ***asio thread pool*** 23 | reposted 1000001 in 1381.58 ms 24 | reposted 1000001 in 1390.35 ms 25 | reposted 1000001 in 1391.84 ms 26 | reposted 1000001 in 1393.19 ms 27 | 28 | See benchmark/benchmark.cpp for benchmark code. 29 | 30 | All code except [MPMCBoundedQueue](https://github.com/inkooboo/thread-pool-cpp/blob/master/include/thread_pool/mpmc_bounded_queue.hpp) 31 | is under MIT license. 32 | 33 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #benchmark 2 | 3 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 4 | 5 | add_executable(benchmark benchmark.cpp) 6 | target_link_libraries(benchmark pthread) 7 | 8 | -------------------------------------------------------------------------------- /benchmark/asio_thread_pool.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ASIO_THREAD_POOL_HPP 2 | #define ASIO_THREAD_POOL_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class AsioThreadPool 12 | { 13 | public: 14 | inline AsioThreadPool(size_t threads); 15 | 16 | inline ~AsioThreadPool() 17 | { 18 | stop(); 19 | } 20 | 21 | inline void joinThreadPool(); 22 | 23 | template 24 | inline void post(Handler &&handler) 25 | { 26 | m_io_svc.post(handler); 27 | } 28 | 29 | private: 30 | inline void start(); 31 | inline void stop(); 32 | inline void worker_thread_func(); 33 | 34 | boost::asio::io_service m_io_svc; 35 | std::unique_ptr m_work; 36 | 37 | std::vector m_threads; 38 | }; 39 | 40 | inline AsioThreadPool::AsioThreadPool(size_t threads) 41 | : m_threads(threads) 42 | { 43 | start(); 44 | } 45 | 46 | inline void AsioThreadPool::start() 47 | { 48 | m_work.reset(new boost::asio::io_service::work(m_io_svc)); 49 | 50 | for (auto &i : m_threads) 51 | { 52 | i = std::thread(&AsioThreadPool::worker_thread_func, this); 53 | } 54 | 55 | } 56 | 57 | inline void AsioThreadPool::stop() 58 | { 59 | m_work.reset(); 60 | 61 | m_io_svc.stop(); 62 | 63 | for (auto &i : m_threads) 64 | { 65 | if (i.joinable()) 66 | { 67 | i.join(); 68 | } 69 | } 70 | } 71 | 72 | inline void AsioThreadPool::joinThreadPool() 73 | { 74 | m_io_svc.run(); 75 | } 76 | 77 | inline void AsioThreadPool::worker_thread_func() 78 | { 79 | joinThreadPool(); 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /benchmark/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef WITH_ASIO 4 | #include 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace tp; 14 | 15 | static const size_t CONCURRENCY = 16; 16 | static const size_t REPOST_COUNT = 1000000; 17 | 18 | struct Heavy 19 | { 20 | bool verbose; 21 | std::vector resource; 22 | 23 | Heavy(bool verbose = false) : verbose(verbose), resource(100 * 1024 * 1024) 24 | { 25 | if(verbose) 26 | { 27 | std::cout << "heavy default constructor" << std::endl; 28 | } 29 | } 30 | 31 | Heavy(const Heavy& o) : verbose(o.verbose), resource(o.resource) 32 | { 33 | if(verbose) 34 | { 35 | std::cout << "heavy copy constructor" << std::endl; 36 | } 37 | } 38 | 39 | Heavy(Heavy&& o) : verbose(o.verbose), resource(std::move(o.resource)) 40 | { 41 | if(verbose) 42 | { 43 | std::cout << "heavy move constructor" << std::endl; 44 | } 45 | } 46 | 47 | Heavy& operator==(const Heavy& o) 48 | { 49 | verbose = o.verbose; 50 | resource = o.resource; 51 | if(verbose) 52 | { 53 | std::cout << "heavy copy operator" << std::endl; 54 | } 55 | return *this; 56 | } 57 | 58 | Heavy& operator==(const Heavy&& o) 59 | { 60 | verbose = o.verbose; 61 | resource = std::move(o.resource); 62 | if(verbose) 63 | { 64 | std::cout << "heavy move operator" << std::endl; 65 | } 66 | return *this; 67 | } 68 | 69 | ~Heavy() 70 | { 71 | if(verbose) 72 | { 73 | std::cout << "heavy destructor. " 74 | << (resource.size() ? "Owns resource" 75 | : "Doesn't own resource") 76 | << std::endl; 77 | } 78 | } 79 | }; 80 | 81 | 82 | struct RepostJob 83 | { 84 | //Heavy heavy; 85 | 86 | ThreadPool* thread_pool; 87 | #ifdef WITH_ASIO 88 | AsioThreadPool* asio_thread_pool; 89 | #endif 90 | 91 | volatile size_t counter; 92 | long long int begin_count; 93 | std::promise* waiter; 94 | 95 | RepostJob(ThreadPool* thread_pool, std::promise* waiter) 96 | : thread_pool(thread_pool) 97 | #ifdef WITH_ASIO 98 | , 99 | asio_thread_pool(0) 100 | #endif 101 | , 102 | counter(0), waiter(waiter) 103 | { 104 | begin_count = std::chrono::high_resolution_clock::now() 105 | .time_since_epoch() 106 | .count(); 107 | } 108 | 109 | #ifdef WITH_ASIO 110 | RepostJob(AsioThreadPool* asio_thread_pool, std::promise* waiter) 111 | : thread_pool(0), asio_thread_pool(asio_thread_pool), counter(0), 112 | waiter(waiter) 113 | { 114 | begin_count = std::chrono::high_resolution_clock::now() 115 | .time_since_epoch() 116 | .count(); 117 | } 118 | #endif 119 | 120 | void operator()() 121 | { 122 | if(++counter < REPOST_COUNT) 123 | { 124 | #ifdef WITH_ASIO 125 | if(asio_thread_pool) 126 | { 127 | asio_thread_pool->post(*this); 128 | return; 129 | } 130 | #endif 131 | if(thread_pool) 132 | { 133 | thread_pool->post(*this); 134 | return; 135 | } 136 | } 137 | else 138 | { 139 | long long int end_count = std::chrono::high_resolution_clock::now() 140 | .time_since_epoch() 141 | .count(); 142 | std::cout << "reposted " << counter << " in " 143 | << (double)(end_count - begin_count) / (double)1000000 144 | << " ms" << std::endl; 145 | waiter->set_value(); 146 | } 147 | } 148 | }; 149 | 150 | int main(int, const char* []) 151 | { 152 | std::cout << "Benchmark job reposting" << std::endl; 153 | 154 | { 155 | std::cout << "***thread pool cpp***" << std::endl; 156 | 157 | std::promise waiters[CONCURRENCY]; 158 | ThreadPool thread_pool; 159 | for(auto& waiter : waiters) 160 | { 161 | thread_pool.post(RepostJob(&thread_pool, &waiter)); 162 | } 163 | 164 | for(auto& waiter : waiters) 165 | { 166 | waiter.get_future().wait(); 167 | } 168 | } 169 | 170 | #ifdef WITH_ASIO 171 | { 172 | std::cout << "***asio thread pool***" << std::endl; 173 | 174 | size_t workers_count = std::thread::hardware_concurrency(); 175 | if(0 == workers_count) 176 | { 177 | workers_count = 1; 178 | } 179 | 180 | AsioThreadPool asio_thread_pool(workers_count); 181 | 182 | std::promise waiters[CONCURRENCY]; 183 | for(auto& waiter : waiters) 184 | { 185 | asio_thread_pool.post(RepostJob(&asio_thread_pool, &waiter)); 186 | } 187 | 188 | for(auto& waiter : waiters) 189 | { 190 | waiter.get_future().wait(); 191 | } 192 | } 193 | #endif 194 | 195 | return 0; 196 | } 197 | -------------------------------------------------------------------------------- /cmake/Findthread-pool-cpp.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2016 Vittorio Romeo 2 | # License: Academic Free License ("AFL") v. 3.0 3 | # AFL License page: http://opensource.org/licenses/AFL-3.0 4 | # http://vittorioromeo.info | vittorio.romeo@outlook.com 5 | 6 | # Adapted from Louise Dionne's FindHana.cmake file 7 | 8 | # Copyright Louis Dionne 2015 9 | # Distributed under the Boost Software License, Version 1.0. 10 | # (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) 11 | 12 | # TODO: document variables: 13 | # THREAD_POOL_CPP_FOUND 14 | # THREAD_POOL_CPP_INCLUDE_DIR 15 | # THREAD_POOL_CPP_CLONE_DIR 16 | # THREAD_POOL_CPP_ENABLE_TESTS 17 | 18 | find_path( 19 | THREAD_POOL_CPP_INCLUDE_DIR 20 | 21 | NAMES vrm/core.hpp 22 | DOC "Include directory for the thread-pool-cpp library" 23 | 24 | PATH_SUFFIXES include/ 25 | 26 | PATHS 27 | "${PROJECT_SOURCE_DIR}/../thread-pool-cpp/" 28 | "${PROJECT_SOURCE_DIR}/../thread_pool_cpp/" 29 | ${THREAD_POOL_CPP_ROOT} 30 | $ENV{THREAD_POOL_CPP_ROOT} 31 | /usr/local/ 32 | /usr/ 33 | /sw/ 34 | /opt/local/ 35 | /opt/csw/ 36 | /opt/ 37 | "${PROJECT_SOURCE_DIR}/extlibs/thread-pool-cpp/" 38 | "${PROJECT_SOURCE_DIR}/extlibs/thread_pool_cpp/" 39 | "${CMAKE_CURRENT_LIST_DIR}/../../" 40 | 41 | NO_DEFAULT_PATH 42 | ) 43 | 44 | if (NOT EXISTS "${THREAD_POOL_CPP_INCLUDE_DIR}" AND DEFINED THREAD_POOL_CPP_CLONE_DIR) 45 | set(_build_dir "${CMAKE_CURRENT_BINARY_DIR}/thread-pool-cpp") 46 | 47 | if (DEFINED THREAD_POOL_CPP_ENABLE_TESTS) 48 | set(_test_cmd ${CMAKE_COMMAND} --build ${_build_dir} --target check) 49 | else() 50 | set(_test_cmd "") 51 | endif() 52 | 53 | include(ExternalProject) 54 | ExternalProject_Add(thread_pool_cpp 55 | PREFIX ${_build_dir} 56 | STAMP_DIR ${_build_dir}/_stamps 57 | TMP_DIR ${_build_dir}/_tmp 58 | 59 | # Since we don't have any files to download, we set the DOWNLOAD_DIR 60 | # to TMP_DIR to avoid creating a useless empty directory. 61 | DOWNLOAD_DIR ${_build_dir}/_tmp 62 | 63 | # Download step 64 | GIT_REPOSITORY https://github.com/SuperV1234/thread-pool-cpp 65 | GIT_TAG master 66 | TIMEOUT 20 67 | 68 | # Configure step 69 | SOURCE_DIR "${THREAD_POOL_CPP_CLONE_DIR}" 70 | CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} 71 | 72 | BINARY_DIR "${_build_dir}" 73 | BUILD_COMMAND "" 74 | 75 | # Install step (nothing to be done) 76 | INSTALL_COMMAND "" 77 | 78 | # Test step 79 | TEST_COMMAND ${_test_cmd} 80 | ) 81 | 82 | set(THREAD_POOL_CPP_INCLUDE_DIR "${THREAD_POOL_CPP_CLONE_DIR}/include") 83 | endif() 84 | 85 | include(FindPackageHandleStandardArgs) 86 | find_package_handle_standard_args(THREAD_POOL_CPP DEFAULT_MSG THREAD_POOL_CPP_INCLUDE_DIR) 87 | -------------------------------------------------------------------------------- /include/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /include/thread_pool/fixed_function.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace tp 9 | { 10 | 11 | /** 12 | * @brief The FixedFunction class implements 13 | * functional object. 14 | * This function is analog of 'std::function' with limited capabilities: 15 | * - It supports only move semantics. 16 | * - The size of functional objects is limited to storage size. 17 | * Due to limitations above it is much faster on creation and copying than 18 | * std::function. 19 | */ 20 | template 21 | class FixedFunction; 22 | 23 | template 24 | class FixedFunction 25 | { 26 | 27 | typedef R (*func_ptr_type)(ARGS...); 28 | 29 | public: 30 | FixedFunction() 31 | : m_function_ptr(nullptr), m_method_ptr(nullptr), 32 | m_alloc_ptr(nullptr) 33 | { 34 | } 35 | 36 | /** 37 | * @brief FixedFunction Constructor from functional object. 38 | * @param object Functor object will be stored in the internal storage 39 | * using move constructor. Unmovable objects are prohibited explicitly. 40 | */ 41 | template 42 | FixedFunction(FUNC&& object) 43 | : FixedFunction() 44 | { 45 | typedef typename std::remove_reference::type unref_type; 46 | 47 | static_assert(sizeof(unref_type) < STORAGE_SIZE, 48 | "functional object doesn't fit into internal storage"); 49 | static_assert(std::is_move_constructible::value, 50 | "Should be of movable type"); 51 | 52 | m_method_ptr = []( 53 | void* object_ptr, func_ptr_type, ARGS... args) -> R 54 | { 55 | return static_cast(object_ptr) 56 | -> 57 | operator()(args...); 58 | }; 59 | 60 | m_alloc_ptr = [](void* storage_ptr, void* object_ptr) 61 | { 62 | if(object_ptr) 63 | { 64 | unref_type* x_object = static_cast(object_ptr); 65 | new(storage_ptr) unref_type(std::move(*x_object)); 66 | } 67 | else 68 | { 69 | static_cast(storage_ptr)->~unref_type(); 70 | } 71 | }; 72 | 73 | m_alloc_ptr(&m_storage, &object); 74 | } 75 | 76 | /** 77 | * @brief FixedFunction Constructor from free function or static member. 78 | */ 79 | template 80 | FixedFunction(RET (*func_ptr)(PARAMS...)) 81 | : FixedFunction() 82 | { 83 | m_function_ptr = func_ptr; 84 | m_method_ptr = [](void*, func_ptr_type f_ptr, ARGS... args) -> R 85 | { 86 | return static_cast(f_ptr)(args...); 87 | }; 88 | } 89 | 90 | FixedFunction(FixedFunction&& o) : FixedFunction() 91 | { 92 | moveFromOther(o); 93 | } 94 | 95 | FixedFunction& operator=(FixedFunction&& o) 96 | { 97 | moveFromOther(o); 98 | return *this; 99 | } 100 | 101 | ~FixedFunction() 102 | { 103 | if(m_alloc_ptr) m_alloc_ptr(&m_storage, nullptr); 104 | } 105 | 106 | /** 107 | * @brief operator () Execute stored functional object. 108 | * @throws std::runtime_error if no functional object is stored. 109 | */ 110 | R operator()(ARGS... args) 111 | { 112 | if(!m_method_ptr) throw std::runtime_error("call of empty functor"); 113 | return m_method_ptr(&m_storage, m_function_ptr, args...); 114 | } 115 | 116 | private: 117 | FixedFunction& operator=(const FixedFunction&) = delete; 118 | FixedFunction(const FixedFunction&) = delete; 119 | 120 | union 121 | { 122 | typename std::aligned_storage::type 123 | m_storage; 124 | func_ptr_type m_function_ptr; 125 | }; 126 | 127 | typedef R (*method_type)( 128 | void* object_ptr, func_ptr_type free_func_ptr, ARGS... args); 129 | method_type m_method_ptr; 130 | 131 | typedef void (*alloc_type)(void* storage_ptr, void* object_ptr); 132 | alloc_type m_alloc_ptr; 133 | 134 | void moveFromOther(FixedFunction& o) 135 | { 136 | if(this == &o) return; 137 | 138 | if(m_alloc_ptr) 139 | { 140 | m_alloc_ptr(&m_storage, nullptr); 141 | m_alloc_ptr = nullptr; 142 | } 143 | else 144 | { 145 | m_function_ptr = nullptr; 146 | } 147 | 148 | m_method_ptr = o.m_method_ptr; 149 | o.m_method_ptr = nullptr; 150 | 151 | if(o.m_alloc_ptr) 152 | { 153 | m_alloc_ptr = o.m_alloc_ptr; 154 | m_alloc_ptr(&m_storage, &o.m_storage); 155 | } 156 | else 157 | { 158 | m_function_ptr = o.m_function_ptr; 159 | } 160 | } 161 | }; 162 | 163 | } 164 | -------------------------------------------------------------------------------- /include/thread_pool/mpmc_bounded_queue.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2011 Dmitry Vyukov. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided 5 | // that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, 8 | // this list of 9 | // conditions and the following disclaimer. 10 | // 11 | // 2. Redistributions in binary form must reproduce the above copyright 12 | // notice, this list 13 | // of conditions and the following disclaimer in the documentation and/or 14 | // other materials 15 | // provided with the distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED 18 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 20 | // EVENT 21 | // SHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 24 | // OR 25 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | // LIABILITY, 27 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 28 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 29 | // ADVISED OF 30 | // THE POSSIBILITY OF SUCH DAMAGE. 31 | // 32 | // The views and conclusions contained in the software and documentation are 33 | // those of the authors and 34 | // should not be interpreted as representing official policies, either expressed 35 | // or implied, of Dmitry Vyukov. 36 | 37 | #pragma once 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | namespace tp 45 | { 46 | 47 | /** 48 | * @brief The MPMCBoundedQueue class implements bounded 49 | * multi-producers/multi-consumers lock-free queue. 50 | * Doesn't accept non-movable types as T. 51 | * Inspired by Dmitry Vyukov's mpmc queue. 52 | * http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue 53 | */ 54 | template 55 | class MPMCBoundedQueue 56 | { 57 | static_assert( 58 | std::is_move_constructible::value, "Should be of movable type"); 59 | 60 | public: 61 | /** 62 | * @brief MPMCBoundedQueue Constructor. 63 | * @param size Power of 2 number - queue length. 64 | * @throws std::invalid_argument if size is bad. 65 | */ 66 | explicit MPMCBoundedQueue(size_t size); 67 | 68 | /** 69 | * @brief Move ctor implementation. 70 | */ 71 | MPMCBoundedQueue(MPMCBoundedQueue&& rhs) noexcept; 72 | 73 | /** 74 | * @brief Move assignment implementaion. 75 | */ 76 | MPMCBoundedQueue& operator=(MPMCBoundedQueue&& rhs) noexcept; 77 | 78 | /** 79 | * @brief push Push data to queue. 80 | * @param data Data to be pushed. 81 | * @return true on success. 82 | */ 83 | template 84 | bool push(U&& data); 85 | 86 | /** 87 | * @brief pop Pop data from queue. 88 | * @param data Place to store popped data. 89 | * @return true on sucess. 90 | */ 91 | bool pop(T& data); 92 | 93 | private: 94 | struct Cell 95 | { 96 | std::atomic sequence; 97 | T data; 98 | 99 | Cell() = default; 100 | 101 | Cell(const Cell&) = delete; 102 | Cell& operator=(const Cell&) = delete; 103 | 104 | Cell(Cell&& rhs) 105 | : sequence(rhs.sequence.load()), data(std::move(rhs.data)) 106 | { 107 | } 108 | 109 | Cell& operator=(Cell&& rhs) 110 | { 111 | sequence = rhs.sequence.load(); 112 | data = std::move(rhs.data); 113 | 114 | return *this; 115 | } 116 | }; 117 | 118 | private: 119 | typedef char Cacheline[64]; 120 | 121 | Cacheline pad0; 122 | std::vector m_buffer; 123 | /* const */ size_t m_buffer_mask; 124 | Cacheline pad1; 125 | std::atomic m_enqueue_pos; 126 | Cacheline pad2; 127 | std::atomic m_dequeue_pos; 128 | Cacheline pad3; 129 | }; 130 | 131 | 132 | /// Implementation 133 | 134 | template 135 | inline MPMCBoundedQueue::MPMCBoundedQueue(size_t size) 136 | : m_buffer(size), m_buffer_mask(size - 1), m_enqueue_pos(0), 137 | m_dequeue_pos(0) 138 | { 139 | bool size_is_power_of_2 = (size >= 2) && ((size & (size - 1)) == 0); 140 | if(!size_is_power_of_2) 141 | { 142 | throw std::invalid_argument("buffer size should be a power of 2"); 143 | } 144 | 145 | for(size_t i = 0; i < size; ++i) 146 | { 147 | m_buffer[i].sequence = i; 148 | } 149 | } 150 | 151 | template 152 | inline MPMCBoundedQueue::MPMCBoundedQueue(MPMCBoundedQueue&& rhs) noexcept 153 | { 154 | *this = rhs; 155 | } 156 | 157 | template 158 | inline MPMCBoundedQueue& MPMCBoundedQueue::operator=(MPMCBoundedQueue&& rhs) noexcept 159 | { 160 | if (this != &rhs) 161 | { 162 | m_buffer = std::move(rhs.m_buffer); 163 | m_buffer_mask = std::move(rhs.m_buffer_mask); 164 | m_enqueue_pos = rhs.m_enqueue_pos.load(); 165 | m_dequeue_pos = rhs.m_dequeue_pos.load(); 166 | } 167 | return *this; 168 | } 169 | 170 | template 171 | template 172 | inline bool MPMCBoundedQueue::push(U&& data) 173 | { 174 | Cell* cell; 175 | size_t pos = m_enqueue_pos.load(std::memory_order_relaxed); 176 | for(;;) 177 | { 178 | cell = &m_buffer[pos & m_buffer_mask]; 179 | size_t seq = cell->sequence.load(std::memory_order_acquire); 180 | intptr_t dif = (intptr_t)seq - (intptr_t)pos; 181 | if(dif == 0) 182 | { 183 | if(m_enqueue_pos.compare_exchange_weak( 184 | pos, pos + 1, std::memory_order_relaxed)) 185 | { 186 | break; 187 | } 188 | } 189 | else if(dif < 0) 190 | { 191 | return false; 192 | } 193 | else 194 | { 195 | pos = m_enqueue_pos.load(std::memory_order_relaxed); 196 | } 197 | } 198 | 199 | cell->data = std::forward(data); 200 | 201 | cell->sequence.store(pos + 1, std::memory_order_release); 202 | 203 | return true; 204 | } 205 | 206 | template 207 | inline bool MPMCBoundedQueue::pop(T& data) 208 | { 209 | Cell* cell; 210 | size_t pos = m_dequeue_pos.load(std::memory_order_relaxed); 211 | for(;;) 212 | { 213 | cell = &m_buffer[pos & m_buffer_mask]; 214 | size_t seq = cell->sequence.load(std::memory_order_acquire); 215 | intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1); 216 | if(dif == 0) 217 | { 218 | if(m_dequeue_pos.compare_exchange_weak( 219 | pos, pos + 1, std::memory_order_relaxed)) 220 | { 221 | break; 222 | } 223 | } 224 | else if(dif < 0) 225 | { 226 | return false; 227 | } 228 | else 229 | { 230 | pos = m_dequeue_pos.load(std::memory_order_relaxed); 231 | } 232 | } 233 | 234 | data = std::move(cell->data); 235 | 236 | cell->sequence.store( 237 | pos + m_buffer_mask + 1, std::memory_order_release); 238 | 239 | return true; 240 | } 241 | 242 | } 243 | -------------------------------------------------------------------------------- /include/thread_pool/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace tp 14 | { 15 | 16 | template class Queue> 17 | class ThreadPoolImpl; 18 | using ThreadPool = ThreadPoolImpl, 19 | MPMCBoundedQueue>; 20 | 21 | /** 22 | * @brief The ThreadPool class implements thread pool pattern. 23 | * It is highly scalable and fast. 24 | * It is header only. 25 | * It implements both work-stealing and work-distribution balancing 26 | * startegies. 27 | * It implements cooperative scheduling strategy for tasks. 28 | */ 29 | template class Queue> 30 | class ThreadPoolImpl { 31 | public: 32 | /** 33 | * @brief ThreadPool Construct and start new thread pool. 34 | * @param options Creation options. 35 | */ 36 | explicit ThreadPoolImpl( 37 | const ThreadPoolOptions& options = ThreadPoolOptions()); 38 | 39 | /** 40 | * @brief Move ctor implementation. 41 | */ 42 | ThreadPoolImpl(ThreadPoolImpl&& rhs) noexcept; 43 | 44 | /** 45 | * @brief ~ThreadPool Stop all workers and destroy thread pool. 46 | */ 47 | ~ThreadPoolImpl(); 48 | 49 | /** 50 | * @brief Move assignment implementaion. 51 | */ 52 | ThreadPoolImpl& operator=(ThreadPoolImpl&& rhs) noexcept; 53 | 54 | /** 55 | * @brief post Try post job to thread pool. 56 | * @param handler Handler to be called from thread pool worker. It has 57 | * to be callable as 'handler()'. 58 | * @return 'true' on success, false otherwise. 59 | * @note All exceptions thrown by handler will be suppressed. 60 | */ 61 | template 62 | bool tryPost(Handler&& handler); 63 | 64 | /** 65 | * @brief post Post job to thread pool. 66 | * @param handler Handler to be called from thread pool worker. It has 67 | * to be callable as 'handler()'. 68 | * @throw std::overflow_error if worker's queue is full. 69 | * @note All exceptions thrown by handler will be suppressed. 70 | */ 71 | template 72 | void post(Handler&& handler); 73 | 74 | private: 75 | Worker& getWorker(); 76 | 77 | std::vector>> m_workers; 78 | std::atomic m_next_worker; 79 | }; 80 | 81 | 82 | /// Implementation 83 | 84 | template class Queue> 85 | inline ThreadPoolImpl::ThreadPoolImpl( 86 | const ThreadPoolOptions& options) 87 | : m_workers(options.threadCount()) 88 | , m_next_worker(0) 89 | { 90 | for(auto& worker_ptr : m_workers) 91 | { 92 | worker_ptr.reset(new Worker(options.queueSize())); 93 | } 94 | 95 | for(size_t i = 0; i < m_workers.size(); ++i) 96 | { 97 | Worker* steal_donor = 98 | m_workers[(i + 1) % m_workers.size()].get(); 99 | m_workers[i]->start(i, steal_donor); 100 | } 101 | } 102 | 103 | template class Queue> 104 | inline ThreadPoolImpl::ThreadPoolImpl(ThreadPoolImpl&& rhs) noexcept 105 | { 106 | *this = rhs; 107 | } 108 | 109 | template class Queue> 110 | inline ThreadPoolImpl::~ThreadPoolImpl() 111 | { 112 | for (auto& worker_ptr : m_workers) 113 | { 114 | worker_ptr->stop(); 115 | } 116 | } 117 | 118 | template class Queue> 119 | inline ThreadPoolImpl& 120 | ThreadPoolImpl::operator=(ThreadPoolImpl&& rhs) noexcept 121 | { 122 | if (this != &rhs) 123 | { 124 | m_workers = std::move(rhs.m_workers); 125 | m_next_worker = rhs.m_next_worker.load(); 126 | } 127 | return *this; 128 | } 129 | 130 | template class Queue> 131 | template 132 | inline bool ThreadPoolImpl::tryPost(Handler&& handler) 133 | { 134 | return getWorker().post(std::forward(handler)); 135 | } 136 | 137 | template class Queue> 138 | template 139 | inline void ThreadPoolImpl::post(Handler&& handler) 140 | { 141 | const auto ok = tryPost(std::forward(handler)); 142 | if (!ok) 143 | { 144 | throw std::runtime_error("thread pool queue is full"); 145 | } 146 | } 147 | 148 | template class Queue> 149 | inline Worker& ThreadPoolImpl::getWorker() 150 | { 151 | auto id = Worker::getWorkerIdForCurrentThread(); 152 | 153 | if (id > m_workers.size()) 154 | { 155 | id = m_next_worker.fetch_add(1, std::memory_order_relaxed) % 156 | m_workers.size(); 157 | } 158 | 159 | return *m_workers[id]; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /include/thread_pool/thread_pool_options.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace tp 7 | { 8 | 9 | /** 10 | * @brief The ThreadPoolOptions class provides creation options for 11 | * ThreadPool. 12 | */ 13 | class ThreadPoolOptions 14 | { 15 | public: 16 | /** 17 | * @brief ThreadPoolOptions Construct default options for thread pool. 18 | */ 19 | ThreadPoolOptions(); 20 | 21 | /** 22 | * @brief setThreadCount Set thread count. 23 | * @param count Number of threads to be created. 24 | */ 25 | void setThreadCount(size_t count); 26 | 27 | /** 28 | * @brief setQueueSize Set single worker queue size. 29 | * @param count Maximum length of queue of single worker. 30 | */ 31 | void setQueueSize(size_t size); 32 | 33 | /** 34 | * @brief threadCount Return thread count. 35 | */ 36 | size_t threadCount() const; 37 | 38 | /** 39 | * @brief queueSize Return single worker queue size. 40 | */ 41 | size_t queueSize() const; 42 | 43 | private: 44 | size_t m_thread_count; 45 | size_t m_queue_size; 46 | }; 47 | 48 | /// Implementation 49 | 50 | inline ThreadPoolOptions::ThreadPoolOptions() 51 | : m_thread_count(std::max(1u, std::thread::hardware_concurrency())) 52 | , m_queue_size(1024u) 53 | { 54 | } 55 | 56 | inline void ThreadPoolOptions::setThreadCount(size_t count) 57 | { 58 | m_thread_count = std::max(1u, count); 59 | } 60 | 61 | inline void ThreadPoolOptions::setQueueSize(size_t size) 62 | { 63 | m_queue_size = std::max(1u, size); 64 | } 65 | 66 | inline size_t ThreadPoolOptions::threadCount() const 67 | { 68 | return m_thread_count; 69 | } 70 | 71 | inline size_t ThreadPoolOptions::queueSize() const 72 | { 73 | return m_queue_size; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /include/thread_pool/worker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace tp 7 | { 8 | 9 | /** 10 | * @brief The Worker class owns task queue and executing thread. 11 | * In thread it tries to pop task from queue. If queue is empty then it tries 12 | * to steal task from the sibling worker. If steal was unsuccessful then spins 13 | * with one millisecond delay. 14 | */ 15 | template class Queue> 16 | class Worker 17 | { 18 | public: 19 | /** 20 | * @brief Worker Constructor. 21 | * @param queue_size Length of undelaying task queue. 22 | */ 23 | explicit Worker(size_t queue_size); 24 | 25 | /** 26 | * @brief Move ctor implementation. 27 | */ 28 | Worker(Worker&& rhs) noexcept; 29 | 30 | /** 31 | * @brief Move assignment implementaion. 32 | */ 33 | Worker& operator=(Worker&& rhs) noexcept; 34 | 35 | /** 36 | * @brief start Create the executing thread and start tasks execution. 37 | * @param id Worker ID. 38 | * @param steal_donor Sibling worker to steal task from it. 39 | */ 40 | void start(size_t id, Worker* steal_donor); 41 | 42 | /** 43 | * @brief stop Stop all worker's thread and stealing activity. 44 | * Waits until the executing thread became finished. 45 | */ 46 | void stop(); 47 | 48 | /** 49 | * @brief post Post task to queue. 50 | * @param handler Handler to be executed in executing thread. 51 | * @return true on success. 52 | */ 53 | template 54 | bool post(Handler&& handler); 55 | 56 | /** 57 | * @brief steal Steal one task from this worker queue. 58 | * @param task Place for stealed task to be stored. 59 | * @return true on success. 60 | */ 61 | bool steal(Task& task); 62 | 63 | /** 64 | * @brief getWorkerIdForCurrentThread Return worker ID associated with 65 | * current thread if exists. 66 | * @return Worker ID. 67 | */ 68 | static size_t getWorkerIdForCurrentThread(); 69 | 70 | private: 71 | /** 72 | * @brief threadFunc Executing thread function. 73 | * @param id Worker ID to be associated with this thread. 74 | * @param steal_donor Sibling worker to steal task from it. 75 | */ 76 | void threadFunc(size_t id, Worker* steal_donor); 77 | 78 | Queue m_queue; 79 | std::atomic m_running_flag; 80 | std::thread m_thread; 81 | }; 82 | 83 | 84 | /// Implementation 85 | 86 | namespace detail 87 | { 88 | inline size_t* thread_id() 89 | { 90 | static thread_local size_t tss_id = -1u; 91 | return &tss_id; 92 | } 93 | } 94 | 95 | template class Queue> 96 | inline Worker::Worker(size_t queue_size) 97 | : m_queue(queue_size) 98 | , m_running_flag(true) 99 | { 100 | } 101 | 102 | template class Queue> 103 | inline Worker::Worker(Worker&& rhs) noexcept 104 | { 105 | *this = rhs; 106 | } 107 | 108 | template class Queue> 109 | inline Worker& Worker::operator=(Worker&& rhs) noexcept 110 | { 111 | if (this != &rhs) 112 | { 113 | m_queue = std::move(rhs.m_queue); 114 | m_running_flag = rhs.m_running_flag.load(); 115 | m_thread = std::move(rhs.m_thread); 116 | } 117 | return *this; 118 | } 119 | 120 | template class Queue> 121 | inline void Worker::stop() 122 | { 123 | m_running_flag.store(false, std::memory_order_relaxed); 124 | m_thread.join(); 125 | } 126 | 127 | template class Queue> 128 | inline void Worker::start(size_t id, Worker* steal_donor) 129 | { 130 | m_thread = std::thread(&Worker::threadFunc, this, id, steal_donor); 131 | } 132 | 133 | template class Queue> 134 | inline size_t Worker::getWorkerIdForCurrentThread() 135 | { 136 | return *detail::thread_id(); 137 | } 138 | 139 | template class Queue> 140 | template 141 | inline bool Worker::post(Handler&& handler) 142 | { 143 | return m_queue.push(std::forward(handler)); 144 | } 145 | 146 | template class Queue> 147 | inline bool Worker::steal(Task& task) 148 | { 149 | return m_queue.pop(task); 150 | } 151 | 152 | template class Queue> 153 | inline void Worker::threadFunc(size_t id, Worker* steal_donor) 154 | { 155 | *detail::thread_id() = id; 156 | 157 | Task handler; 158 | 159 | while (m_running_flag.load(std::memory_order_relaxed)) 160 | { 161 | if (m_queue.pop(handler) || steal_donor->steal(handler)) 162 | { 163 | try 164 | { 165 | handler(); 166 | } 167 | catch(...) 168 | { 169 | // suppress all exceptions 170 | } 171 | } 172 | else 173 | { 174 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 175 | } 176 | } 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #tests 2 | 3 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 4 | 5 | function(build_test test_name) 6 | add_executable(${test_name}_test ${ARGN}) 7 | target_link_libraries(${test_name}_test pthread gtest gtest_main) 8 | add_test(${test_name} ./${test_name}_test) 9 | endfunction() 10 | 11 | build_test(fixed_function fixed_function.t.cpp) 12 | build_test(thread_pool thread_pool.t.cpp) 13 | build_test(thread_pool_options thread_pool_options.t.cpp) 14 | -------------------------------------------------------------------------------- /tests/fixed_function.t.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | int test_free_func(int i) 10 | { 11 | return i; 12 | } 13 | 14 | template 15 | T test_free_func_template(T p) 16 | { 17 | return p; 18 | } 19 | 20 | void test_void(int &p, int v) 21 | { 22 | p = v; 23 | } 24 | 25 | struct A { 26 | int b(const int &p) 27 | { 28 | return p; 29 | } 30 | void c(int &i) 31 | { 32 | i = 43; 33 | } 34 | }; 35 | 36 | template 37 | struct Foo 38 | { 39 | template 40 | U bar(U p) 41 | { 42 | return p + payload; 43 | } 44 | 45 | T payload; 46 | }; 47 | 48 | template 49 | void print_overhead() 50 | { 51 | using func_type = tp::FixedFunction; 52 | int t_s = sizeof(T); 53 | int f_s = sizeof(func_type); 54 | std::cout << " - for type size " << t_s << "\n" 55 | << " function size is " << f_s << "\n" 56 | << " overhead is " << float(f_s - t_s)/t_s * 100 << "%\n"; 57 | } 58 | 59 | static std::string str_fun() 60 | { 61 | return "123"; 62 | } 63 | 64 | TEST(FixedFunction, Overhead) 65 | { 66 | print_overhead(); 67 | print_overhead(); 68 | print_overhead(); 69 | print_overhead(); 70 | print_overhead(); 71 | } 72 | 73 | TEST(FixedFunction, allocDealloc) 74 | { 75 | static size_t def = 0; 76 | static size_t cop = 0; 77 | static size_t mov = 0; 78 | static size_t cop_ass = 0; 79 | static size_t mov_ass = 0; 80 | static size_t destroyed = 0; 81 | struct cnt { 82 | std::string payload; 83 | cnt() { def++; } 84 | cnt(const cnt &o) { payload = o.payload; cop++;} 85 | cnt(cnt &&o) { payload = std::move(o.payload); mov++;} 86 | cnt & operator=(const cnt &o) { payload = o.payload; cop_ass++; return *this; } 87 | cnt & operator=(cnt &&o) { payload = std::move(o.payload); mov_ass++; return *this; } 88 | ~cnt() { destroyed++; } 89 | std::string operator()() { return payload; } 90 | }; 91 | 92 | { 93 | cnt c1; 94 | c1.payload = "xyz"; 95 | tp::FixedFunction f1(c1); 96 | ASSERT_EQ(std::string("xyz"), f1()); 97 | 98 | tp::FixedFunction f2; 99 | f2 = std::move(f1); 100 | ASSERT_EQ(std::string("xyz"), f2()); 101 | 102 | tp::FixedFunction f3(std::move(f2)); 103 | ASSERT_EQ(std::string("xyz"), f3()); 104 | 105 | tp::FixedFunction f4(str_fun); 106 | ASSERT_EQ(std::string("123"), f4()); 107 | 108 | f4 = std::move(f3); 109 | ASSERT_EQ(std::string("xyz"), f4()); 110 | 111 | cnt c2; 112 | c2.payload = "qwe"; 113 | f4 = std::move(tp::FixedFunction(c2)); 114 | ASSERT_EQ(std::string("qwe"), f4()); 115 | } 116 | 117 | ASSERT_EQ(def + cop + mov, destroyed); 118 | ASSERT_EQ(2, def); 119 | ASSERT_EQ(0, cop); 120 | ASSERT_EQ(6, mov); 121 | ASSERT_EQ(0, cop_ass); 122 | ASSERT_EQ(0, mov_ass); 123 | } 124 | 125 | TEST(FixedFunction, freeFunc) 126 | { 127 | tp::FixedFunction f(test_free_func); 128 | auto f1 = std::move(f); 129 | ASSERT_EQ(3, f1(3)); 130 | }; 131 | 132 | TEST(FixedFunction, freeFuncTemplate) 133 | { 134 | tp::FixedFunction f(test_free_func_template); 135 | auto f1 = std::move(f); 136 | ASSERT_EQ(std::string("abc"), f1("abc")); 137 | } 138 | 139 | 140 | TEST(FixedFunction, voidFunc) 141 | { 142 | tp::FixedFunction f(test_void); 143 | auto f1 = std::move(f); 144 | int p = 0; 145 | f1(p, 42); 146 | ASSERT_EQ(42, p); 147 | } 148 | 149 | TEST(FixedFunction, classMethodVoid) 150 | { 151 | using namespace std::placeholders; 152 | A a; 153 | int i = 0; 154 | tp::FixedFunction f(std::bind(&A::c, &a, _1)); 155 | auto f1 = std::move(f); 156 | f1(i); 157 | ASSERT_EQ(43, i); 158 | } 159 | 160 | TEST(FixedFunction, classMethod1) 161 | { 162 | using namespace std::placeholders; 163 | A a; 164 | tp::FixedFunction f(std::bind(&A::b, &a, _1)); 165 | auto f1 = std::move(f); 166 | ASSERT_EQ(4, f1(4)); 167 | } 168 | 169 | TEST(FixedFunction, classMethod2) 170 | { 171 | using namespace std::placeholders; 172 | Foo foo; 173 | foo.payload = 1.f; 174 | tp::FixedFunction f(std::bind(&Foo::bar, &foo, _1)); 175 | auto f1 = std::move(f); 176 | ASSERT_EQ(2, f1(1)); 177 | } 178 | 179 | TEST(FixedFunction, lambda) 180 | { 181 | const std::string s1 = "s1"; 182 | tp::FixedFunction f([&s1]() 183 | { 184 | return s1; 185 | }); 186 | auto f1 = std::move(f); 187 | ASSERT_EQ(s1, f1()); 188 | } 189 | 190 | int main(int argc, char **argv) { 191 | ::testing::InitGoogleTest(&argc, argv); 192 | return RUN_ALL_TESTS(); 193 | } 194 | 195 | 196 | -------------------------------------------------------------------------------- /tests/thread_pool.t.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace TestLinkage { 11 | size_t getWorkerIdForCurrentThread() 12 | { 13 | return *tp::detail::thread_id(); 14 | } 15 | 16 | size_t getWorkerIdForCurrentThread2() 17 | { 18 | return tp::Worker, tp::MPMCBoundedQueue>::getWorkerIdForCurrentThread(); 19 | } 20 | } 21 | 22 | TEST(ThreadPool, postJob) 23 | { 24 | tp::ThreadPool pool; 25 | 26 | std::packaged_task t([]() 27 | { 28 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 29 | return 42; 30 | }); 31 | 32 | std::future r = t.get_future(); 33 | 34 | pool.post(t); 35 | 36 | ASSERT_EQ(42, r.get()); 37 | } 38 | 39 | int main(int argc, char **argv) { 40 | ::testing::InitGoogleTest(&argc, argv); 41 | return RUN_ALL_TESTS(); 42 | } 43 | -------------------------------------------------------------------------------- /tests/thread_pool_options.t.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST(ThreadPoolOptions, ctor) 8 | { 9 | tp::ThreadPoolOptions options; 10 | 11 | ASSERT_EQ(1024, options.queueSize()); 12 | ASSERT_EQ(std::max(1u, std::thread::hardware_concurrency()), 13 | options.threadCount()); 14 | } 15 | 16 | TEST(ThreadPoolOptions, modification) 17 | { 18 | tp::ThreadPoolOptions options; 19 | 20 | options.setThreadCount(5); 21 | ASSERT_EQ(5, options.threadCount()); 22 | 23 | options.setQueueSize(32); 24 | ASSERT_EQ(32, options.queueSize()); 25 | } 26 | 27 | int main(int argc, char **argv) { 28 | ::testing::InitGoogleTest(&argc, argv); 29 | return RUN_ALL_TESTS(); 30 | } 31 | --------------------------------------------------------------------------------