diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..b070356f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,124 @@ +S# Lineage: ruslo/hunter test scripts +# test + +# OSX/Linux (https://github.com/travis-ci-tester/toolchain-table) + +language: + - cpp + +# Container-based infrastructure (Linux) +# * https://docs.travis-ci.com/user/migrating-from-legacy/#How-can-I-use-container-based-infrastructure%3F +sudo: + - false + +# Install packages differs for container-based infrastructure +# * https://docs.travis-ci.com/user/migrating-from-legacy/#How-do-I-install-APT-sources-and-packages%3F +# * http://stackoverflow.com/a/30925448/2288008 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - python3 + + # python3-pip package is not available, use 'easy_install3': + # * https://github.com/travis-ci/apt-package-whitelist/issues/768 + - python3-setuptools # easy_install3 + + # https://github.com/travis-ci-tester/travis-test-clang-cxx-11 + - libstdc++-4.8-dev + + # https://github.com/travis-ci-tester/travis-test-gcc-cxx-11 + - g++-4.8 + + # For Qt + - libegl1-mesa-dev + + # Packages for Android development: http://superuser.com/a/360398/252568 + - libncurses5:i386 + - libstdc++6:i386 + - zlib1g:i386 + +matrix: + include: + # Linux { + - os: linux + env: CONFIG=Release TOOLCHAIN=gcc-4-8-pic-hid-sections HAS_CPP14=OFF + - os: linux + env: CONFIG=Debug TOOLCHAIN=gcc-4-8-pic-hid-sections HAS_CPP14=OFF + - os: linux + env: CONFIG=Release TOOLCHAIN=android-ndk-r10e-api-19-armeabi-v7a-neon-hid-sections HAS_CPP14=OFF + - os: linux + env: CONFIG=Debug TOOLCHAIN=android-ndk-r10e-api-19-armeabi-v7a-neon-hid-sections HAS_CPP14=OFF + # } + + # OSX { + - os: osx + env: CONFIG=Release TOOLCHAIN=osx-10-11-hid-sections HAS_CPP14=OFF + - os: osx + env: CONFIG=Debug TOOLCHAIN=osx-10-11-hid-sections HAS_CPP14=OFF + - os: osx + env: CONFIG=Release TOOLCHAIN=ios-nocodesign-9-3-device-hid-sections HAS_CPP14=OFF + - os: osx + env: CONFIG=Debug TOOLCHAIN=ios-nocodesign-9-3-device-hid-sections HAS_CPP14=OFF + - os: osx + osx_image: xcode8.1 + env: CONFIG=Release TOOLCHAIN=osx-10-12-sanitize-address-hid-sections HAS_CPP14=OFF + - os: osx + osx_image: xcode8.1 + env: CONFIG=Debug TOOLCHAIN=osx-10-12-sanitize-address-hid-sections HAS_CPP14=OFF + - os: osx + env: CONFIG=Release TOOLCHAIN=android-ndk-r10e-api-19-armeabi-v7a-neon-hid-sections HAS_CPP14=OFF + - os: osx + env: CONFIG=Debug TOOLCHAIN=android-ndk-r10e-api-19-armeabi-v7a-neon-hid-sections HAS_CPP14=OFF + # } + +# disable the default submodule logic to support local modification of .gitmodules paths +git: + submodules: false + +install: + # Info about OS + - uname -a + + # Install Python 3 + - if [[ "`uname`" == "Darwin" ]]; then travis_retry brew install python3; fi + + # Install Python package 'requests' + # 'easy_install3' is not installed by 'brew install python3' on OS X 10.9 Maverick + - if [[ "`uname`" == "Darwin" ]]; then pip3 install requests; fi + - if [[ "`uname`" == "Linux" ]]; then travis_retry easy_install3 --user requests==2.10.0; fi + + # Install latest Polly toolchains and scripts + - wget https://github.com/ruslo/polly/archive/master.zip + - unzip master.zip + - POLLY_ROOT="`pwd`/polly-master" + - export PATH="${POLLY_ROOT}/bin:${PATH}" + + # Install dependencies (CMake, Android NDK) + - install-ci-dependencies.py + + # Tune locations + - export PATH="`pwd`/_ci/cmake/bin:${PATH}" + + # Installed if toolchain is Android (otherwise directory doesn't exist) + - export ANDROID_NDK_r10e="`pwd`/_ci/android-ndk-r10e" + +script: + + # '--ios-{multiarch,combined}' do nothing for non-iOS builds + - > + polly.py + --toolchain ${TOOLCHAIN} + --config ${CONFIG} + --verbose + --ios-multiarch --ios-combined + --fwd + THREAD_POOL_CPP_BUILD_TESTS=ON + THREAD_POOL_CPP_HAS_CPP14=${HAS_CPP14} + --test + --jobs 2 + +branches: + except: + - /^pr\..*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 50c8f2d9..d14e45ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,23 +1,111 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.3) # support visibility settings -list(APPEND CMAKE_MODULE_PATH - "${CMAKE_CURRENT_SOURCE_DIR}/cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules") -project(thread-pool-cpp) -add_definitions(-std=c++14 -Wall -Werror -O3) +project(thread-pool-cpp VERSION 1.1.0) +option(THREAD_POOL_CPP_BUILD_TESTS "Build tests." OFF) +option(THREAD_POOL_CPP_BUILD_BENCHMARKS "Build benchmarks." OFF) -# Get all include files +if(THREAD_POOL_CPP_BUILD_TESTS) + include(CTest) +endif() + +# Variable used in: benchmark, tests set(THREAD_POOL_CPP_INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/") include_directories("${THREAD_POOL_CPP_INC_DIR}") -file(GLOB_RECURSE INSTALL_FILES_LIST "${THREAD_POOL_CPP_INC_DIR}/*") -add_subdirectory(tests) -add_subdirectory(benchmark) +# If true C++11 thread_local support exists, we will use it: +include(thread_pool_has_thread_local_storage) +thread_pool_has_thread_local_storage(THREAD_POOL_HAS_THREAD_LOCAL_STORAGE) +message("THREAD_POOL_HAS_THREAD_LOCAL_STORAGE: ${THREAD_POOL_HAS_THREAD_LOCAL_STORAGE}") + +if(NOT THREAD_POOL_HAS_THREAD_LOCAL_STORAGE) + # Else, we will check for backups + include(thread_pool_has_thread_storage) + thread_pool_has_thread_storage(THREAD_POOL_HAS_THREAD_STORAGE) + message("THREAD_POOL_HAS_THREAD_STORAGE: ${THREAD_POOL_HAS_THREAD_STORAGE}") + if(NOT THREAD_POOL_HAS_THREAD_STORAGE) + message(FATAL_ERROR "Compiler does not support: thread_local, __thread, or declspec(thread)") + endif() +endif() + +# http://stackoverflow.com/a/11583676 +add_library(thread-pool-cpp INTERFACE) + +option(THREAD_POOL_CPP_HAS_CPP14 "Has true C++14 support." ON) + +if(NOT MSVC) + if(THREAD_POOL_CPP_HAS_CPP14) + target_compile_definitions(thread-pool-cpp INTERFACE -std=c++14 -Wall -Werror -O3) + else() + target_compile_definitions(thread-pool-cpp INTERFACE -std=c++1y -Wall -Werror -O3) + endif() +else() + if(THREAD_POOL_CPP_HAS_CPP14) + target_compile_definitions(thread-pool-cpp INTERFACE /std:c++14) + else() + target_compile_definitions(thread-pool-cpp INTERFACE /std:c++1y) + endif() +endif() + +if(THREAD_POOL_CPP_BUILD_TESTS) + add_subdirectory(tests) +endif() + +if(THREAD_POOL_CPP_BUILD_BENCHMARKS) + add_subdirectory(benchmark) +endif() + +#### install + +set(TARGET_NAME thread-pool-cpp) + +set(config_install_dir "lib/cmake/${TARGET_NAME}") +set(include_install_dir "include") + +set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") + +# Configuration +set(version_config "${generated_dir}/${TARGET_NAME}ConfigVersion.cmake") +set(project_config "${generated_dir}/${TARGET_NAME}Config.cmake") +set(targets_export_name "${TARGET_NAME}Targets") +set(namespace "${TARGET_NAME}::") + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${version_config}" COMPATIBILITY SameMajorVersion + ) + +configure_package_config_file( + "cmake/Config.cmake.in" + "${project_config}" + INSTALL_DESTINATION "${config_install_dir}" + ) + +install( + TARGETS thread-pool-cpp + EXPORT "${targets_export_name}" + LIBRARY DESTINATION "lib" + ARCHIVE DESTINATION "lib" + RUNTIME DESTINATION "bin" + INCLUDES DESTINATION "${include_install_dir}" + ) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/thread_pool + DESTINATION "${include_install_dir}" + FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" + ) + +install( + FILES "${project_config}" "${version_config}" + DESTINATION "${config_install_dir}" + ) -# Install as header-only library -set_source_files_properties(${INSTALL_FILES_LIST} PROPERTIES HEADER_FILE_ONLY 1) -add_library(HEADER_ONLY_TARGET STATIC ${INSTALL_FILES_LIST}) -set_target_properties(HEADER_ONLY_TARGET PROPERTIES LINKER_LANGUAGE CXX) -install(DIRECTORY ${THREAD_POOL_CPP_INC_DIR} DESTINATION "include") +install( + EXPORT "${targets_export_name}" + NAMESPACE "${namespace}" + DESTINATION "${config_install_dir}" + ) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..9b78cddf --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,63 @@ +# Lineage: ruslo/hunter tests scripts +# test + +os: Visual Studio 2015 + +environment: + + matrix: + + - TOOLCHAIN: "vs-14-2015-sdk-8-1" + CONFIG: Release + HAS_CPP14: OFF + + - TOOLCHAIN: "vs-14-2015-sdk-8-1" + CONFIG: Debug + HAS_CPP14: OFF + + - TOOLCHAIN: "vs-14-2015-win64-sdk-8-1" + CONFIG: Release + HAS_CPP14: OFF + + - TOOLCHAIN: "vs-14-2015-win64-sdk-8-1" + CONFIG: Debug + HAS_CPP14: OFF + +install: + # Python 3 + - cmd: set PATH=C:\Python34-x64;C:\Python34-x64\Scripts;%PATH% + + # Install Python package 'requests' + - cmd: pip install requests + + # Install latest Polly toolchains and scripts + - cmd: appveyor DownloadFile https://github.com/ruslo/polly/archive/master.zip + - cmd: 7z x master.zip + - cmd: set POLLY_ROOT=%cd%\polly-master + + # Install dependencies (CMake, Ninja) + - cmd: python %POLLY_ROOT%\bin\install-ci-dependencies.py + + # Tune locations + - cmd: set PATH=%cd%\_ci\cmake\bin;%PATH% + - cmd: set PATH=%cd%\_ci\ninja;%PATH% + + # Remove entry with sh.exe from PATH to fix error with MinGW toolchain + # (For MinGW make to work correctly sh.exe must NOT be in your path) + # * http://stackoverflow.com/a/3870338/2288008 + - cmd: set PATH=%PATH:C:\Program Files\Git\usr\bin;=% + + # Use MinGW from Qt tools because version is higher + # * http://www.appveyor.com/docs/installed-software#qt + - cmd: set MINGW_PATH=C:\Qt\Tools\mingw492_32\bin + + # MSYS2 location + - cmd: set MSYS_PATH=C:\msys64\usr\bin + +build_script: + + - cmd: bin\build-appveyor.cmd "%CONFIG%" "%TOOLCHAIN%" "%HAS_CPP14%" + +branches: + except: + - /^pr\..*/ diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 8d3377bd..6cac7e5c 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -1,11 +1,4 @@ #benchmark -include_directories("${THREAD_POOL_CPP_INC_DIR}") - -find_package(Boost REQUIRED COMPONENTS system) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${Boost_INCLUDE_DIR}) add_executable(benchmark benchmark.cpp) -target_link_libraries(benchmark ${Boost_LIBRARIES} pthread) - diff --git a/benchmark/asio_thread_pool.hpp b/benchmark/asio_thread_pool.hpp deleted file mode 100644 index 3d19411d..00000000 --- a/benchmark/asio_thread_pool.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef ASIO_THREAD_POOL_HPP -#define ASIO_THREAD_POOL_HPP - -#include - -#include -#include -#include -#include - -class AsioThreadPool -{ -public: - inline AsioThreadPool(size_t threads); - - inline ~AsioThreadPool() - { - stop(); - } - - inline void joinThreadPool(); - - template - inline void post(Handler &&handler) - { - m_io_svc.post(handler); - } - -private: - inline void start(); - inline void stop(); - inline void worker_thread_func(); - - boost::asio::io_service m_io_svc; - std::unique_ptr m_work; - - std::vector m_threads; -}; - -inline AsioThreadPool::AsioThreadPool(size_t threads) - : m_threads(threads) -{ - start(); -} - -inline void AsioThreadPool::start() -{ - m_work.reset(new boost::asio::io_service::work(m_io_svc)); - - for (auto &i : m_threads) - { - i = std::thread(&AsioThreadPool::worker_thread_func, this); - } - -} - -inline void AsioThreadPool::stop() -{ - m_work.reset(); - - m_io_svc.stop(); - - for (auto &i : m_threads) - { - if (i.joinable()) - { - i.join(); - } - } -} - -inline void AsioThreadPool::joinThreadPool() -{ - m_io_svc.run(); -} - -inline void AsioThreadPool::worker_thread_func() -{ - joinThreadPool(); -} - -#endif diff --git a/benchmark/benchmark.cpp b/benchmark/benchmark.cpp index a6df324b..d066cfdd 100644 --- a/benchmark/benchmark.cpp +++ b/benchmark/benchmark.cpp @@ -1,10 +1,4 @@ -//#define WITHOUT_ASIO 1 - -#include - -#ifndef WITHOUT_ASIO -#include -#endif +#include #include #include @@ -16,8 +10,8 @@ using namespace tp; using ThreadPoolStd = ThreadPool<>; -static const size_t CONCURRENCY = 16; -static const size_t REPOST_COUNT = 1000000; +static const std::size_t CONCURRENCY = 16; +static const std::size_t REPOST_COUNT = 1000000; struct Heavy { @@ -88,50 +82,25 @@ struct RepostJob // Heavy heavy; ThreadPoolStd* thread_pool; -#ifndef WITHOUT_ASIO - AsioThreadPool* asio_thread_pool; -#endif - volatile size_t counter; + volatile std::size_t counter; long long int begin_count; std::promise* waiter; RepostJob(ThreadPoolStd* thread_pool, std::promise* waiter) : thread_pool(thread_pool) -#ifndef WITHOUT_ASIO - , - asio_thread_pool(0) -#endif - , - counter(0), waiter(waiter) - { - begin_count = std::chrono::high_resolution_clock::now() - .time_since_epoch() - .count(); - } - -#ifndef WITHOUT_ASIO - RepostJob(AsioThreadPool* asio_thread_pool, std::promise* waiter) - : thread_pool(0), asio_thread_pool(asio_thread_pool), counter(0), - waiter(waiter) + , counter(0) + , waiter(waiter) { begin_count = std::chrono::high_resolution_clock::now() .time_since_epoch() .count(); } -#endif void operator()() { if(counter++ < REPOST_COUNT) { -#ifndef WITHOUT_ASIO - if(asio_thread_pool) - { - asio_thread_pool->post(*this); - return; - } -#endif if(thread_pool) { thread_pool->post(*this); @@ -171,30 +140,5 @@ int main(int, const char* []) } } -#ifndef WITHOUT_ASIO - { - std::cout << "***asio thread pool***" << std::endl; - - size_t workers_count = std::thread::hardware_concurrency(); - if(0 == workers_count) - { - workers_count = 1; - } - - AsioThreadPool asio_thread_pool(workers_count); - - std::promise waiters[CONCURRENCY]; - for(auto& waiter : waiters) - { - asio_thread_pool.post(RepostJob(&asio_thread_pool, &waiter)); - } - - for(auto& waiter : waiters) - { - waiter.get_future().wait(); - } - } -#endif - return 0; } diff --git a/bin/build-appveyor.cmd b/bin/build-appveyor.cmd new file mode 100755 index 00000000..7b045db8 --- /dev/null +++ b/bin/build-appveyor.cmd @@ -0,0 +1,16 @@ +:: Name: build-appveyor.cmd +:: Purpose: Support readable multi-line polly.py build commands +:: +:: Multi-line commands are not currently supported directly in appveyor.yml files +:: +:: See: http://stackoverflow.com/a/37647169 + +echo POLLY_ROOT %POLLY_ROOT% + +python %POLLY_ROOT%\bin\polly.py ^ +--verbose ^ +--config "%1%" ^ +--toolchain "%2%" ^ +--fwd THREAD_POOL_CPP_BUILD_TESTS=ON THREAD_POOL_CPP_HAS_CPP14="%3" ^ +--test + diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in new file mode 100644 index 00000000..9b4c9ee0 --- /dev/null +++ b/cmake/Config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/cmake/Findthread-pool-cpp.cmake b/cmake/Findthread-pool-cpp.cmake deleted file mode 100644 index 1910b9d3..00000000 --- a/cmake/Findthread-pool-cpp.cmake +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright (c) 2015-2016 Vittorio Romeo -# License: Academic Free License ("AFL") v. 3.0 -# AFL License page: http://opensource.org/licenses/AFL-3.0 -# http://vittorioromeo.info | vittorio.romeo@outlook.com - -# Adapted from Louise Dionne's FindHana.cmake file - -# Copyright Louis Dionne 2015 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) - -# TODO: document variables: -# THREAD_POOL_CPP_FOUND -# THREAD_POOL_CPP_INCLUDE_DIR -# THREAD_POOL_CPP_CLONE_DIR -# THREAD_POOL_CPP_ENABLE_TESTS - -find_path( - THREAD_POOL_CPP_INCLUDE_DIR - - NAMES vrm/core.hpp - DOC "Include directory for the thread-pool-cpp library" - - PATH_SUFFIXES include/ - - PATHS - "${PROJECT_SOURCE_DIR}/../thread-pool-cpp/" - "${PROJECT_SOURCE_DIR}/../thread_pool_cpp/" - ${THREAD_POOL_CPP_ROOT} - $ENV{THREAD_POOL_CPP_ROOT} - /usr/local/ - /usr/ - /sw/ - /opt/local/ - /opt/csw/ - /opt/ - "${PROJECT_SOURCE_DIR}/extlibs/thread-pool-cpp/" - "${PROJECT_SOURCE_DIR}/extlibs/thread_pool_cpp/" - "${CMAKE_CURRENT_LIST_DIR}/../../" - - NO_DEFAULT_PATH -) - -if (NOT EXISTS "${THREAD_POOL_CPP_INCLUDE_DIR}" AND DEFINED THREAD_POOL_CPP_CLONE_DIR) - set(_build_dir "${CMAKE_CURRENT_BINARY_DIR}/thread-pool-cpp") - - if (DEFINED THREAD_POOL_CPP_ENABLE_TESTS) - set(_test_cmd ${CMAKE_COMMAND} --build ${_build_dir} --target check) - else() - set(_test_cmd "") - endif() - - include(ExternalProject) - ExternalProject_Add(thread_pool_cpp - PREFIX ${_build_dir} - STAMP_DIR ${_build_dir}/_stamps - TMP_DIR ${_build_dir}/_tmp - - # Since we don't have any files to download, we set the DOWNLOAD_DIR - # to TMP_DIR to avoid creating a useless empty directory. - DOWNLOAD_DIR ${_build_dir}/_tmp - - # Download step - GIT_REPOSITORY https://github.com/SuperV1234/thread-pool-cpp - GIT_TAG master - TIMEOUT 20 - - # Configure step - SOURCE_DIR "${THREAD_POOL_CPP_CLONE_DIR}" - CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - - BINARY_DIR "${_build_dir}" - BUILD_COMMAND "" - - # Install step (nothing to be done) - INSTALL_COMMAND "" - - # Test step - TEST_COMMAND ${_test_cmd} - ) - - set(THREAD_POOL_CPP_INCLUDE_DIR "${THREAD_POOL_CPP_CLONE_DIR}/include") -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(THREAD_POOL_CPP DEFAULT_MSG THREAD_POOL_CPP_INCLUDE_DIR) \ No newline at end of file diff --git a/cmake/Modules/thread_pool_has_thread_local_storage.cmake b/cmake/Modules/thread_pool_has_thread_local_storage.cmake new file mode 100644 index 00000000..b1389796 --- /dev/null +++ b/cmake/Modules/thread_pool_has_thread_local_storage.cmake @@ -0,0 +1,8 @@ +# Check for true C++11 thread_local support +function(thread_pool_has_thread_local_storage varName) + include(CheckCXXSourceCompiles) + check_cxx_source_compiles("static thread_local int tls; int main(void) { return 0; }" + THREAD_POOL_HAS_THREAD_LOCAL_STORAGE + ) + set(${varName} ${THREAD_POOL_HAS_THREAD_LOCAL_STORAGE} PARENT_SCOPE) +endfunction(thread_pool_has_thread_local_storage) diff --git a/cmake/Modules/thread_pool_has_thread_storage.cmake b/cmake/Modules/thread_pool_has_thread_storage.cmake new file mode 100644 index 00000000..9af959c0 --- /dev/null +++ b/cmake/Modules/thread_pool_has_thread_storage.cmake @@ -0,0 +1,16 @@ +# Check for a variety of backups for C++11 thread_local support +function(thread_pool_has_thread_storage varName) + include(CheckCSourceCompiles) + check_c_source_compiles(" +#if defined (__GNUC__) + #define ATTRIBUTE_TLS __thread +#elif defined (_MSC_VER) + #define ATTRIBUTE_TLS __declspec(thread) +#else // !__GNUC__ && !_MSC_VER + #error \"Define a thread local storage qualifier for your compiler/platform!\" +#endif +ATTRIBUTE_TLS int tls; +int main(void) { return 0; +}" HAVE_THREAD_LOCAL_STORAGE) + set(${varName} ${HAVE_THREAD_LOCAL_STORAGE} PARENT_SCOPE) +endfunction(thread_pool_has_thread_storage) diff --git a/include/thread_pool/fixed_function.hpp b/include/thread_pool/fixed_function.hpp index 1f469cd1..3f2fac50 100644 --- a/include/thread_pool/fixed_function.hpp +++ b/include/thread_pool/fixed_function.hpp @@ -17,10 +17,10 @@ namespace tp * Due to limitations above it is much faster on creation and copying than * std::function. */ - template + template class FixedFunction; - template + template class FixedFunction { @@ -119,7 +119,7 @@ namespace tp union { - typename std::aligned_storage::type + typename std::aligned_storage::type m_storage; func_ptr_type m_function_ptr; }; diff --git a/include/thread_pool/mpsc_bounded_queue.hpp b/include/thread_pool/mpsc_bounded_queue.hpp index 1d9c0c95..67531d82 100644 --- a/include/thread_pool/mpsc_bounded_queue.hpp +++ b/include/thread_pool/mpsc_bounded_queue.hpp @@ -62,7 +62,7 @@ namespace tp * @param size Power of 2 number - queue length. * @throws std::invalid_argument if size is bad. */ - explicit MPMCBoundedQueue(size_t size); + explicit MPMCBoundedQueue(std::size_t size); /** * @brief push Push data to queue. @@ -85,7 +85,7 @@ namespace tp struct Cell { - std::atomic sequence; + std::atomic sequence; T data; Cell() = default; @@ -131,11 +131,11 @@ namespace tp Cacheline pad0; std::vector m_buffer; - /* const */ size_t m_buffer_mask; + /* const */ std::size_t m_buffer_mask; Cacheline pad1; - std::atomic m_enqueue_pos; + std::atomic m_enqueue_pos; Cacheline pad2; - std::atomic m_dequeue_pos; + std::atomic m_dequeue_pos; Cacheline pad3; }; @@ -143,7 +143,7 @@ namespace tp /// Implementation template - inline MPMCBoundedQueue::MPMCBoundedQueue(size_t size) + inline MPMCBoundedQueue::MPMCBoundedQueue(std::size_t size) : m_buffer(size), m_buffer_mask(size - 1), m_enqueue_pos(0), m_dequeue_pos(0) { @@ -153,7 +153,7 @@ namespace tp throw std::invalid_argument("buffer size should be a power of 2"); } - for(size_t i = 0; i < size; ++i) + for(std::size_t i = 0; i < size; ++i) { m_buffer[i].sequence = i; } @@ -164,11 +164,11 @@ namespace tp inline bool MPMCBoundedQueue::push(U&& data) { Cell* cell; - size_t pos = m_enqueue_pos.load(std::memory_order_relaxed); + std::size_t pos = m_enqueue_pos.load(std::memory_order_relaxed); for(;;) { cell = &m_buffer[pos & m_buffer_mask]; - size_t seq = cell->sequence.load(std::memory_order_acquire); + std::size_t seq = cell->sequence.load(std::memory_order_acquire); intptr_t dif = (intptr_t)seq - (intptr_t)pos; if(dif == 0) { @@ -199,11 +199,11 @@ namespace tp inline bool MPMCBoundedQueue::pop(T& data) { Cell* cell; - size_t pos = m_dequeue_pos.load(std::memory_order_relaxed); + std::size_t pos = m_dequeue_pos.load(std::memory_order_relaxed); for(;;) { cell = &m_buffer[pos & m_buffer_mask]; - size_t seq = cell->sequence.load(std::memory_order_acquire); + std::size_t seq = cell->sequence.load(std::memory_order_acquire); intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1); if(dif == 0) { diff --git a/include/thread_pool/thread_pool.hpp b/include/thread_pool/thread_pool.hpp index e854eb3a..d7e525d6 100644 --- a/include/thread_pool/thread_pool.hpp +++ b/include/thread_pool/thread_pool.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "./worker.hpp" namespace tp @@ -26,6 +27,15 @@ namespace tp std::size_t worker_queue_size = 1024; }; + /** + * @brief The ThreadPoolSettings struct provides compile-time options for + * ThreadPool. + */ + template struct ThreadPoolSettings + { + static constexpr auto task_size = TASK_SIZE; + }; + /** * @brief The ThreadPool class implements thread pool pattern. * It is highly scalable and fast. @@ -34,16 +44,17 @@ namespace tp * startegies. * It implements cooperative scheduling strategy for tasks. */ - template + template > class ThreadPool { public: + + using FixedWorker = Worker; /** * @brief ThreadPool Construct and start new thread pool. * @param options Creation options. */ - explicit ThreadPool( - const ThreadPoolOptions& options = ThreadPoolOptions()); + explicit ThreadPool(const ThreadPoolOptions& options = ThreadPoolOptions()); /** * @brief ~ThreadPool Stop all workers and destroy thread pool. @@ -69,7 +80,30 @@ namespace tp template void post(Handler&& handler); + /** + * @brief process Post piece of job to thread pool and get future for this job. + * @param handler Handler to be called from thread pool worker. It has to be callable as 'handler()'. + * @return Future which hold handler result or exception thrown. + * @throws std::overflow_error if worker's queue is full. + * @note This method of posting job to thread pool is much slower than 'post()' due to std::future and + * std::packaged_task construction overhead. + */ + template ::type> + typename std::future process(Handler &&handler) + { + std::packaged_task task([handler = std::move(handler)] () { + return handler(); + }); + + std::future result = task.get_future(); + if (!getWorker().post(task)) { + throw std::overflow_error("worker queue is full"); + } + + return result; + } + private: ThreadPool(const ThreadPool&) = delete; ThreadPool& operator=(const ThreadPool&) = delete; @@ -90,9 +124,20 @@ namespace tp } private: - Worker& getWorker(); - std::vector> m_workers; + FixedWorker & getWorker() + { + auto id = FixedWorker::getWorkerIdForCurrentThread(); + + if(id > m_workers.size()) + { + id = m_next_worker.fetch_add(1, std::memory_order_relaxed) % m_workers.size(); + } + + return *m_workers[id]; + } + + std::vector> m_workers; std::atomic m_next_worker; }; @@ -118,12 +163,12 @@ namespace tp m_workers.resize(workers_count); for(auto& worker_ptr : m_workers) { - worker_ptr.reset(new Worker(options.worker_queue_size)); + worker_ptr.reset(new FixedWorker(options.worker_queue_size)); } for(size_t i = 0; i < m_workers.size(); ++i) { - Worker* steal_donor = m_workers[(i + 1) % m_workers.size()].get(); + FixedWorker* steal_donor = m_workers[(i + 1) % m_workers.size()].get(); m_workers[i]->start(i, steal_donor); } } @@ -150,19 +195,6 @@ namespace tp { const auto ok = try_post(std::forward(handler)); assert(ok); - } - - template - inline Worker& ThreadPool::getWorker() - { - auto id = Worker::getWorkerIdForCurrentThread(); - - if(id > m_workers.size()) - { - id = m_next_worker.fetch_add(1, std::memory_order_relaxed) % - m_workers.size(); - } - - return *m_workers[id]; + ((void)ok); } } diff --git a/include/thread_pool/worker.hpp b/include/thread_pool/worker.hpp index 7b6a6a43..6ed03df1 100644 --- a/include/thread_pool/worker.hpp +++ b/include/thread_pool/worker.hpp @@ -1,5 +1,20 @@ #pragma once +// Use C++11 thread_local specifier when available, otherwise revert to platforms +// specific implementations. +// +// http://stackoverflow.com/a/25393790/5724090 +// Static/global variable exists in a per-thread context (thread local storage). +#if THREAD_POOL_HAS_THREAD_LOCAL_STORAGE + #define ATTRIBUTE_TLS thread_local +#elif defined (__GNUC__) + #define ATTRIBUTE_TLS __thread +#elif defined (_MSC_VER) + #define ATTRIBUTE_TLS __declspec(thread) +#else // !__GNUC__ && !_MSC_VER + #error "Define a thread local storage qualifier for your compiler/platform!" +#endif + #include #include #include "./fixed_function.hpp" @@ -14,23 +29,25 @@ namespace tp * unsuccessful * then spins with one millisecond delay. */ + + template class Worker { public: - using Task = FixedFunction; + using Task = FixedFunction; /** * @brief Worker Constructor. * @param queue_size Length of undelaying task queue. */ - explicit Worker(size_t queue_size); + explicit Worker(std::size_t queue_size); /** * @brief start Create the executing thread and start tasks execution. * @param id Worker ID. * @param steal_donor Sibling worker to steal task from it. */ - void start(size_t id, Worker* steal_donor); + void start(std::size_t id, Worker* steal_donor); /** * @brief stop Stop all worker's thread and stealing activity. @@ -44,7 +61,7 @@ namespace tp * @return true on success. */ template - bool post(Handler&& handler); + inline bool post(Handler&& handler); /** * @brief steal Steal one task from this worker queue. @@ -58,21 +75,21 @@ namespace tp * current thread if exists. * @return Worker ID. */ - static size_t getWorkerIdForCurrentThread(); + static std::size_t getWorkerIdForCurrentThread(); private: - Worker(const Worker&) = delete; - Worker& operator=(const Worker&) = delete; + Worker(const Worker&) = delete; + Worker& operator=(const Worker&) = delete; public: - Worker(Worker&& rhs) + Worker(Worker&& rhs) : m_queue(std::move(rhs.m_queue)), m_running_flag(rhs.m_running_flag.load()), m_thread(std::move(rhs.m_thread)) { } - Worker& operator=(Worker&& rhs) + Worker& operator=(Worker&& rhs) { m_queue = std::move(rhs.m_queue); m_running_flag = rhs.m_running_flag.load(); @@ -87,7 +104,7 @@ namespace tp * @param id Worker ID to be associated with this thread. * @param steal_donor Sibling worker to steal task from it. */ - void threadFunc(size_t id, Worker* steal_donor); + void threadFunc(std::size_t id, Worker* steal_donor); MPMCBoundedQueue m_queue; std::atomic m_running_flag; @@ -99,52 +116,60 @@ namespace tp namespace detail { - inline size_t* thread_id() + inline std::size_t* thread_id() { - static thread_local size_t tss_id = -1u; + static ATTRIBUTE_TLS std::size_t tss_id = -1u; return &tss_id; } } - inline Worker::Worker(size_t queue_size) + template + inline Worker::Worker(std::size_t queue_size) : m_queue(queue_size), m_running_flag(true) { } - inline void Worker::stop() + template + inline void Worker::stop() { m_running_flag.store(false, std::memory_order_relaxed); m_thread.join(); } - inline void Worker::start(size_t id, Worker* steal_donor) + template + inline void Worker::start(std::size_t id, Worker* steal_donor) { - m_thread = std::thread(&Worker::threadFunc, this, id, steal_donor); + m_thread = std::thread(&Worker::threadFunc, this, id, steal_donor); } - inline size_t Worker::getWorkerIdForCurrentThread() + template + inline std::size_t Worker::getWorkerIdForCurrentThread() { return *detail::thread_id(); } + template template - inline bool Worker::post(Handler&& handler) + inline bool Worker::post(Handler&& handler) { return m_queue.push(std::forward(handler)); } - - inline bool Worker::steal(Task& task) + + template + inline bool Worker::steal(Task& task) { return m_queue.pop(task); } - inline void Worker::threadFunc(size_t id, Worker* steal_donor) + template + inline void Worker::threadFunc(std::size_t id, Worker* steal_donor) { *detail::thread_id() = id; Task handler; while(m_running_flag.load(std::memory_order_relaxed)) + { if(m_queue.pop(handler) || steal_donor->steal(handler)) { try @@ -159,5 +184,6 @@ namespace tp { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a1a62930..dcf793f2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,21 +1,21 @@ #tests -include_directories("${THREAD_POOL_CPP_INC_DIR}") -include_directories("${CMAKE_CURRENT_SOURCE_DIR}") +enable_testing() -add_executable(fixed_function_test fixed_function.t.cpp) +# Explicit thread linking should support all targets: +# what(): Enable multithreading to use std::thread: Operation not permitted +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) -target_link_libraries(fixed_function_test) -add_custom_command( - TARGET fixed_function_test - POST_BUILD - COMMAND ./fixed_function_test -) +# Build tests +add_executable(fixed_function_test fixed_function.t.cpp) +target_link_libraries(fixed_function_test Threads::Threads) add_executable(thread_pool_test thread_pool.t.cpp thread_pool2.t.cpp) -target_link_libraries(thread_pool_test pthread) -add_custom_command( - TARGET thread_pool_test - POST_BUILD - COMMAND ./thread_pool_test -) +target_link_libraries(thread_pool_test Threads::Threads) + +# Run tests for host == target platforms +if(NOT IOS AND NOT ANDROID) + add_test(NAME FixedFunctionTest COMMAND fixed_function_test) + add_test(NAME ThreadPoolTest COMMAND thread_pool_test) +endif() diff --git a/tests/fixed_function.t.cpp b/tests/fixed_function.t.cpp index d24acaf7..1e8bcfce 100644 --- a/tests/fixed_function.t.cpp +++ b/tests/fixed_function.t.cpp @@ -1,5 +1,5 @@ #include -#include +#include "test.hpp" #include #include @@ -107,7 +107,7 @@ int main() cnt c2; c2.payload = "qwe"; - f4 = std::move(FixedFunction(c2)); + f4 = FixedFunction(c2); ASSERT(std::string("qwe") == f4()); } diff --git a/tests/thread_pool.t.cpp b/tests/thread_pool.t.cpp index 731001f5..b5535dc5 100644 --- a/tests/thread_pool.t.cpp +++ b/tests/thread_pool.t.cpp @@ -1,5 +1,5 @@ #include -#include +#include "test.hpp" #include #include diff --git a/tests/thread_pool2.t.cpp b/tests/thread_pool2.t.cpp index ac45eaed..51e00c0b 100644 --- a/tests/thread_pool2.t.cpp +++ b/tests/thread_pool2.t.cpp @@ -9,5 +9,5 @@ size_t getWorkerIdForCurrentThread() size_t getWorkerIdForCurrentThread2() { - return Worker::getWorkerIdForCurrentThread(); + return Worker<>::getWorkerIdForCurrentThread(); }