├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build └── vs2019 │ ├── oqpi.natvis │ ├── oqpi.sln │ ├── oqpi.vcxproj │ ├── oqpi.vcxproj.filters │ ├── oqpi_tests.vcxproj │ └── oqpi_tests.vcxproj.filters ├── include ├── oqpi.hpp └── oqpi │ ├── concurrent_queue.hpp │ ├── empty_layer.hpp │ ├── error_handling.hpp │ ├── parallel_algorithms.hpp │ ├── parallel_algorithms │ ├── atomic_partitioner.hpp │ ├── base_partitioner.hpp │ ├── parallel_for.hpp │ └── simple_partitioner.hpp │ ├── platform.hpp │ ├── scheduling.hpp │ ├── scheduling │ ├── context_container.hpp │ ├── group_context.hpp │ ├── parallel_group.hpp │ ├── scheduler.hpp │ ├── sequence_group.hpp │ ├── task.hpp │ ├── task_base.hpp │ ├── task_context.hpp │ ├── task_group.hpp │ ├── task_group_base.hpp │ ├── task_handle.hpp │ ├── task_notifier.hpp │ ├── task_result.hpp │ ├── task_type.hpp │ ├── worker.hpp │ ├── worker_base.hpp │ └── worker_context.hpp │ ├── scheduling_helpers.hpp │ ├── synchronization.hpp │ ├── synchronization │ ├── event.hpp │ ├── interface │ │ ├── interface_event.hpp │ │ ├── interface_mutex.hpp │ │ └── interface_semaphore.hpp │ ├── mutex.hpp │ ├── posix │ │ ├── posix_event.hpp │ │ ├── posix_mutex.hpp │ │ ├── posix_semaphore.hpp │ │ ├── posix_semaphore_wrapper.hpp │ │ └── posix_sync.hpp │ ├── semaphore.hpp │ ├── sync.hpp │ ├── sync_common.hpp │ └── win │ │ ├── win_event.hpp │ │ ├── win_mutex.hpp │ │ ├── win_semaphore.hpp │ │ └── win_sync.hpp │ ├── threading.hpp │ └── threading │ ├── interface_thread.hpp │ ├── posix_thread.hpp │ ├── this_thread.hpp │ ├── thread.hpp │ ├── thread_attributes.hpp │ └── win_thread.hpp └── tests ├── catch_amalgamated.cpp ├── catch_amalgamated.hpp ├── event_tests.hpp ├── mutex_tests.hpp ├── oqpi_tests.cpp ├── parallel_algorithms_tests.hpp ├── scheduling_tests.hpp ├── semaphore_tests.hpp ├── sync_tests.hpp ├── test_utils.hpp ├── threading_tests.hpp └── timer_contexts.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.opendb 3 | *.TMP 4 | temp/ 5 | *.sdf 6 | bin/ 7 | *.user 8 | build/vs2019/.vs/ 9 | cmake-build* 10 | .idea 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(oqpi) 4 | 5 | find_package(Threads REQUIRED) 6 | find_library(LIBRT rt REQUIRED) 7 | 8 | set(CMAKE_CXX_COMPILER clang++) 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 11 | #Set debug mode using _GLIBCXX_DEBUG macro 12 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG") 13 | 14 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 15 | set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}" CACHE PATH "..." FORCE) 16 | endif() 17 | 18 | ##############################oqpi_tests############################## 19 | add_executable(oqpi_tests tests/oqpi_tests.cpp tests/catch_amalgamated.cpp) 20 | 21 | target_include_directories(oqpi_tests PRIVATE include) 22 | 23 | # Link with lrt 24 | target_link_libraries(oqpi_tests PRIVATE ${LIBRT}) 25 | 26 | # Link with pthread 27 | set(THREADS_PREFER_FLAG ON) 28 | target_link_libraries(oqpi_tests PRIVATE Threads::Threads) 29 | 30 | install(TARGETS oqpi_tests DESTINATION bin) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 H-EAL 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 | # oqpi 2 | *oqpi* pronounced _occupy_ is a header only concurrency library designed to be as easy to use as possible. 3 | 4 | oqpi helps you reach that 100% core occupancy you're aiming for. 5 | 6 | ## Example 7 | ```cpp 8 | #define OQPI_DEFAULT 9 | #include "oqpi.hpp" 10 | 11 | // Define our toolkit 12 | using oqpi_tk = oqpi::default_helpers; 13 | 14 | int main() 15 | { 16 | // This will start a scheduler with a default workers configuration 17 | oqpi_tk::start_default_scheduler(); 18 | 19 | // oqpi has 3 concepts for tasks: 20 | 21 | // The first one is the unit task: 22 | const auto taskHandle = oqpi_tk::schedule_task 23 | ( 24 | "UnitTask" 25 | , [] { std::cout << "Hello! I'm a unit task!" << std::endl; } 26 | ); 27 | taskHandle.wait(); 28 | 29 | // The second one is the sequence: 30 | const auto sequenceHandle = oqpi_tk::sequence_tasks 31 | ( 32 | "Sequence" 33 | , oqpi_tk::make_task_item("Seq1", [] { std::cout << "Hello! "; }) 34 | , oqpi_tk::make_task_item("Seq2", [] { std::cout << "I am "; }) 35 | , oqpi_tk::make_task_item("Seq3", [] { std::cout << "a sequence!" << std::endl; }) 36 | ); 37 | sequenceHandle.wait(); 38 | 39 | // The third and last one is the fork (or the parallel group) 40 | const auto forkHandle = oqpi_tk::fork_tasks 41 | ( 42 | "Fork" 43 | , oqpi_tk::make_task_item("Fork1", [] { std::cout << "Hello! "; }) 44 | , oqpi_tk::make_task_item("Fork2", [] { std::cout << "I am "; }) 45 | , oqpi_tk::make_task_item("Fork3", [] { std::cout << "a fork!" << std::endl; }) 46 | ); 47 | forkHandle.wait(); 48 | 49 | // Stops the workers and join the threads 50 | oqpi_tk::stop_scheduler(); 51 | } 52 | ``` 53 | 54 | ## Documentation 55 | _Coming soon..._ 56 | -------------------------------------------------------------------------------- /build/vs2019/oqpi.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {_Ptr->uid_} [priority={_Ptr->priority_}, grabbed={(bool)_Ptr->grabbed_._My_val}, done={(bool)_Ptr->done_._My_val}] 7 | 8 | 9 | empty 10 | 11 | 12 | 13 | 14 | 15 | {_Ptr->uid_} [{oqpi::task_type($T1)}, priority={_Ptr->priority_}, grabbed={(bool)_Ptr->grabbed_._My_val}, done={(bool)_Ptr->done_._My_val}] 16 | 17 | 18 | empty 19 | 20 | 21 | 22 | 23 | 24 | Idle 25 | 26 | 27 | Busy {_Mypair._Myval2->hTask_.spTask_._Ptr->uid_} 28 | 29 | 30 | 31 | _Mypair._Myval2->hTask_.spTask_ 32 | 33 | 34 | _Mypair._Myval2->hTask_.spTask_._Ptr->spParentGroup_._Ptr 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /build/vs2019/oqpi.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "oqpi", "oqpi.vcxproj", "{BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "oqpi_tests", "oqpi_tests.vcxproj", "{A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Debug|x64.ActiveCfg = Debug|x64 19 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Debug|x64.Build.0 = Debug|x64 20 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Debug|x86.ActiveCfg = Debug|Win32 21 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Debug|x86.Build.0 = Debug|Win32 22 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Release|x64.ActiveCfg = Release|x64 23 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Release|x64.Build.0 = Release|x64 24 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Release|x86.ActiveCfg = Release|Win32 25 | {BAB3B9FA-0E0C-4E4A-A8C2-5CA6CFF422A4}.Release|x86.Build.0 = Release|Win32 26 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Debug|x64.ActiveCfg = Debug|x64 27 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Debug|x64.Build.0 = Debug|x64 28 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Debug|x86.ActiveCfg = Debug|Win32 29 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Debug|x86.Build.0 = Debug|Win32 30 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Release|x64.ActiveCfg = Release|x64 31 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Release|x64.Build.0 = Release|x64 32 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Release|x86.ActiveCfg = Release|Win32 33 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /build/vs2019/oqpi.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 6 | h;hh;hpp;hxx;hm;inl;inc;xsd 7 | 8 | 9 | {ca81a43e-2070-47cf-9b63-883e4e0d0437} 10 | 11 | 12 | {a584993f-6103-45c0-a0ee-fb70eafe81b8} 13 | 14 | 15 | {ecdbe945-13ab-45f5-afd4-04effcbb8b86} 16 | 17 | 18 | {f9c935e2-64e1-4b65-a016-83d3db886fd7} 19 | 20 | 21 | {d8949f15-c2a9-4c7a-85c7-6285f604207a} 22 | 23 | 24 | {b0b5fc0a-11ff-414d-896c-1b4628b77866} 25 | 26 | 27 | {3c69bfd8-f566-4365-bb69-f9b3b2df27d3} 28 | 29 | 30 | {703cc9c8-4e70-417b-9499-dcef50807375} 31 | 32 | 33 | {e4098ca4-bef3-4996-876b-0d59fdc0d2e6} 34 | 35 | 36 | {b80e97f0-058b-45b4-9448-79c2ae895ed1} 37 | 38 | 39 | {ccd08aa3-26d9-4843-8644-9e1dcf59d308} 40 | 41 | 42 | {a1f45b61-79d6-41b7-b56e-1db52ec8034c} 43 | 44 | 45 | {21dd6cd2-a17f-47e0-9369-f0c4e6f88b1f} 46 | 47 | 48 | 49 | 50 | 51 | include 52 | 53 | 54 | include 55 | 56 | 57 | include 58 | 59 | 60 | include\scheduling\_tasks 61 | 62 | 63 | include\scheduling\_tasks 64 | 65 | 66 | include\scheduling\_tasks 67 | 68 | 69 | include\scheduling\_tasks 70 | 71 | 72 | include\scheduling\_tasks 73 | 74 | 75 | include\scheduling 76 | 77 | 78 | include\synchronization\interface 79 | 80 | 81 | include\synchronization\win 82 | 83 | 84 | include\synchronization 85 | 86 | 87 | include\scheduling\_workers 88 | 89 | 90 | include\scheduling\_tasks 91 | 92 | 93 | include\scheduling\_workers 94 | 95 | 96 | include\scheduling\_workers 97 | 98 | 99 | include\synchronization 100 | 101 | 102 | include\synchronization\win 103 | 104 | 105 | include\synchronization\interface 106 | 107 | 108 | include\scheduling\_groups 109 | 110 | 111 | include\scheduling\_groups 112 | 113 | 114 | include\scheduling\_groups 115 | 116 | 117 | include\scheduling\_groups 118 | 119 | 120 | include\scheduling\_groups 121 | 122 | 123 | include\parallel_algorithms 124 | 125 | 126 | include\parallel_algorithms\_partitioners 127 | 128 | 129 | include\parallel_algorithms\_partitioners 130 | 131 | 132 | include 133 | 134 | 135 | include\threading 136 | 137 | 138 | include\threading 139 | 140 | 141 | include\threading 142 | 143 | 144 | include\threading 145 | 146 | 147 | include\threading\_impl 148 | 149 | 150 | include\_utils 151 | 152 | 153 | include\_utils 154 | 155 | 156 | include\_utils 157 | 158 | 159 | include\scheduling 160 | 161 | 162 | include\scheduling\_tasks 163 | 164 | 165 | include\parallel_algorithms\_partitioners 166 | 167 | 168 | include\_utils 169 | 170 | 171 | include\_utils 172 | 173 | 174 | include\synchronization 175 | 176 | 177 | include\synchronization 178 | 179 | 180 | include\synchronization\interface 181 | 182 | 183 | include\synchronization\win 184 | 185 | 186 | include\threading\_impl 187 | 188 | 189 | include\synchronization\posix 190 | 191 | 192 | include\synchronization\posix 193 | 194 | 195 | include\synchronization\posix 196 | 197 | 198 | include\synchronization\win 199 | 200 | 201 | include\synchronization\posix 202 | 203 | 204 | include\synchronization 205 | 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /build/vs2019/oqpi_tests.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | -D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions) 25 | -D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions) 26 | -D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions) 27 | -D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions) 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {A9C9C6AB-AEF1-49A3-95DA-53338680D7A7} 44 | Win32Proj 45 | oqpi_tests 46 | 10.0 47 | 48 | 49 | 50 | Application 51 | true 52 | v142 53 | Unicode 54 | 55 | 56 | Application 57 | false 58 | v142 59 | true 60 | Unicode 61 | 62 | 63 | Application 64 | true 65 | v142 66 | Unicode 67 | 68 | 69 | Application 70 | false 71 | v142 72 | true 73 | Unicode 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | true 95 | ..\..\temp\$(ProjectName)\$(PlatformTarget)\$(Configuration)\ 96 | $(ProjectName)_x86-d 97 | ..\..\bin\ 98 | 99 | 100 | true 101 | ..\..\temp\$(ProjectName)\$(PlatformTarget)\$(Configuration)\ 102 | $(ProjectName)_x64-d 103 | ..\..\bin\ 104 | 105 | 106 | false 107 | ..\..\temp\$(ProjectName)\$(PlatformTarget)\$(Configuration)\ 108 | ..\..\bin\ 109 | $(ProjectName)_x86 110 | 111 | 112 | false 113 | ..\..\temp\$(ProjectName)\$(PlatformTarget)\$(Configuration)\ 114 | ..\..\bin\ 115 | $(ProjectName)_x64 116 | 117 | 118 | 119 | 120 | 121 | Level4 122 | Disabled 123 | PLATFORM_WIN;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 124 | ..\..\include;%(AdditionalIncludeDirectories) 125 | true 126 | 127 | 128 | Console 129 | true 130 | 131 | 132 | 133 | 134 | 135 | 136 | Level4 137 | Disabled 138 | PLATFORM_WIN;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 139 | ..\..\include;%(AdditionalIncludeDirectories) 140 | true 141 | stdcpp17 142 | 143 | 144 | Console 145 | true 146 | 147 | 148 | 149 | 150 | Level4 151 | 152 | 153 | MaxSpeed 154 | true 155 | true 156 | PLATFORM_WIN;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 157 | ..\..\include;%(AdditionalIncludeDirectories) 158 | true 159 | 160 | 161 | Console 162 | true 163 | true 164 | true 165 | 166 | 167 | 168 | 169 | Level4 170 | 171 | 172 | MaxSpeed 173 | true 174 | true 175 | PLATFORM_WIN;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 176 | ..\..\include;%(AdditionalIncludeDirectories) 177 | true 178 | stdcpp17 179 | 180 | 181 | Console 182 | true 183 | true 184 | true 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /build/vs2019/oqpi_tests.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {67781122-485f-47ac-9bd2-45ed637bd974} 10 | 11 | 12 | {59196ff7-3745-4d5f-8235-12217129aa7b} 13 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | tests\_catch 21 | 22 | 23 | 24 | 25 | tests 26 | 27 | 28 | tests\_catch 29 | 30 | 31 | tests\_tests 32 | 33 | 34 | tests\_tests 35 | 36 | 37 | tests\_tests 38 | 39 | 40 | tests 41 | 42 | 43 | tests\_tests 44 | 45 | 46 | tests\_tests 47 | 48 | 49 | tests\_tests 50 | 51 | 52 | tests\_tests 53 | 54 | 55 | -------------------------------------------------------------------------------- /include/oqpi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/threading.hpp" 4 | #include "oqpi/scheduling.hpp" 5 | #include "oqpi/synchronization.hpp" 6 | #include "oqpi/parallel_algorithms.hpp" 7 | -------------------------------------------------------------------------------- /include/oqpi/concurrent_queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | class concurrent_queue 8 | { 9 | using lock_t = std::lock_guard; 10 | 11 | public: 12 | void push(T &&t) 13 | { 14 | lock_t __l(mutex_); 15 | queue_.emplace(std::move(t)); 16 | } 17 | 18 | void push(const T &t) 19 | { 20 | lock_t __l(mutex_); 21 | queue_.emplace(t); 22 | } 23 | 24 | bool tryPop(typename std::queue::reference v) 25 | { 26 | lock_t __l(mutex_); 27 | if (!queue_.empty()) 28 | { 29 | v = std::move(queue_.front()); 30 | queue_.pop(); 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | bool empty() const 37 | { 38 | return queue_.empty(); 39 | } 40 | 41 | private: 42 | std::mutex mutex_; 43 | std::queue queue_; 44 | }; 45 | -------------------------------------------------------------------------------- /include/oqpi/empty_layer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace oqpi { 4 | 5 | // Defines an empty layer for augmented interfaces 6 | template 7 | struct empty_layer {}; 8 | 9 | // Determines if a given layer is empty: default case, not empty 10 | template typename _Layer> 11 | struct is_empty_layer : public std::false_type {}; 12 | 13 | // Determines if a given layer is empty: empty! 14 | template<> 15 | struct is_empty_layer : public std::true_type {}; 16 | 17 | } /*oqpi*/ 18 | -------------------------------------------------------------------------------- /include/oqpi/parallel_algorithms.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/parallel_algorithms/simple_partitioner.hpp" 4 | #include "oqpi/parallel_algorithms/atomic_partitioner.hpp" 5 | //#include "oqpi/parallel_algorithms/mutable_atomic_partitioner.hpp" 6 | #include "oqpi/parallel_algorithms/parallel_for.hpp" 7 | -------------------------------------------------------------------------------- /include/oqpi/parallel_algorithms/atomic_partitioner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/parallel_algorithms/base_partitioner.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | // Partitioner giving a fixed set of indices to each worker until no more indices are available 12 | // 13 | class atomic_partitioner 14 | : public base_partitioner 15 | { 16 | public: 17 | //------------------------------------------------------------------------------------------ 18 | atomic_partitioner(int32_t firstIndex, int32_t lastIndex, int32_t indicesToGrab, int32_t maxBatches) 19 | : base_partitioner(firstIndex, lastIndex, maxBatches) 20 | , indicesToGrab_(indicesToGrab) 21 | , sharedIndex_(firstIndex) 22 | {} 23 | 24 | //------------------------------------------------------------------------------------------ 25 | atomic_partitioner(int32_t elementsCount, int32_t indicesToGrab, int32_t maxBatches) 26 | : atomic_partitioner(0, elementsCount, indicesToGrab, maxBatches) 27 | {} 28 | 29 | //------------------------------------------------------------------------------------------ 30 | atomic_partitioner(const atomic_partitioner &other) 31 | : base_partitioner(other) 32 | , indicesToGrab_(other.indicesToGrab_) 33 | , sharedIndex_(other.sharedIndex_.load()) 34 | {} 35 | 36 | 37 | //------------------------------------------------------------------------------------------ 38 | // Sets a range of indices for the caller to work on and returns true. 39 | // If no more indices are available returns false. 40 | // The range is warrantied to be returned to one and only one thread. 41 | // 42 | inline bool getNextValidRange(int32_t &firstIndex, int32_t &lastIndex) 43 | { 44 | while (true) 45 | { 46 | // Get a copy of where we're at in the array 47 | auto expectedIndex = sharedIndex_.load(); 48 | // Compute how many indices we could grab 49 | const auto count = std::min(elementCount_ - expectedIndex, indicesToGrab_); 50 | if (count > 0) 51 | { 52 | // There's still something to grab, try to do it, by being the one to increment the shared index. 53 | if (sharedIndex_.compare_exchange_strong(expectedIndex, expectedIndex + count)) 54 | { 55 | // We managed to grab the indices, still some work to do 56 | firstIndex = expectedIndex; 57 | lastIndex = firstIndex + count; 58 | return true; 59 | } 60 | } 61 | else 62 | { 63 | return false; 64 | } 65 | } 66 | } 67 | 68 | 69 | //------------------------------------------------------------------------------------------ 70 | // Number of indices to grab at each run 71 | const int32_t indicesToGrab_; 72 | // Shared index between all threads 73 | std::atomic sharedIndex_; 74 | }; 75 | //---------------------------------------------------------------------------------------------- 76 | 77 | } /*oqpi*/ 78 | -------------------------------------------------------------------------------- /include/oqpi/parallel_algorithms/base_partitioner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace oqpi { 5 | 6 | //------------------------------------------------------------------------------------------ 7 | // Base class for the majority of partitioners. 8 | // Keeps first and last indexes and calculates the total number of elements in that range. 9 | // 10 | class base_partitioner 11 | { 12 | protected: 13 | //------------------------------------------------------------------------------------------ 14 | base_partitioner(int32_t firstIndex, int32_t lastIndex, int32_t maxBatches) 15 | : firstIndex_ (firstIndex) 16 | , lastIndex_ (lastIndex) 17 | , elementCount_ (lastIndex - firstIndex) 18 | , batchCount_ ((elementCount_ < maxBatches) ? elementCount_ : maxBatches) 19 | {} 20 | 21 | //------------------------------------------------------------------------------------------ 22 | base_partitioner(int32_t elementsCount, int32_t maxBatches) 23 | : base_partitioner(0, elementsCount, maxBatches) 24 | {} 25 | 26 | //------------------------------------------------------------------------------------------ 27 | base_partitioner(const base_partitioner &other) 28 | : firstIndex_ (other.firstIndex_) 29 | , lastIndex_ (other.lastIndex_) 30 | , elementCount_ (other.elementCount_) 31 | , batchCount_ (other.batchCount_) 32 | {} 33 | 34 | public: 35 | //------------------------------------------------------------------------------------------ 36 | inline int32_t isValid() const 37 | { 38 | return elementCount_ > 0; 39 | } 40 | 41 | //------------------------------------------------------------------------------------------ 42 | inline int32_t batchCount() const 43 | { 44 | return batchCount_; 45 | } 46 | 47 | //------------------------------------------------------------------------------------------ 48 | inline int32_t elementCount() const 49 | { 50 | return elementCount_; 51 | } 52 | 53 | protected: 54 | const int32_t firstIndex_; 55 | const int32_t lastIndex_; 56 | const int32_t elementCount_; 57 | const int32_t batchCount_; 58 | }; 59 | 60 | } /*oqpi*/ 61 | -------------------------------------------------------------------------------- /include/oqpi/parallel_algorithms/parallel_for.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/scheduling.hpp" 4 | 5 | namespace oqpi { 6 | 7 | namespace details { 8 | 9 | //------------------------------------------------------------------------------------------ 10 | template 11 | struct needs_batch_index 12 | { 13 | struct yes { char a; }; 14 | struct no { char a[2]; }; 15 | 16 | template 17 | static yes test(decltype(std::declval()(int(0), int(0)))*); 18 | 19 | template 20 | static no test(...); 21 | 22 | static const bool value = sizeof(test<_Function>(nullptr)) == sizeof(yes); 23 | }; 24 | //------------------------------------------------------------------------------------------ 25 | template::value> 26 | struct parallel_for_caller; 27 | //------------------------------------------------------------------------------------------ 28 | template 29 | struct parallel_for_caller<_Function, false> 30 | { 31 | static void do_call(_Function &&func, int, int elementIndex) 32 | { 33 | func(elementIndex); 34 | } 35 | }; 36 | //------------------------------------------------------------------------------------------ 37 | template 38 | struct parallel_for_caller<_Function, true> 39 | { 40 | static void do_call(_Function &&func, int batchIndex, int elementIndex) 41 | { 42 | func(batchIndex, elementIndex); 43 | } 44 | }; 45 | //------------------------------------------------------------------------------------------ 46 | template 47 | inline void parallel_for_call(_Function &&func, int batchIndex, int elementIndex) 48 | { 49 | parallel_for_caller<_Function>::do_call(std::forward<_Function>(func), batchIndex, elementIndex); 50 | } 51 | //------------------------------------------------------------------------------------------ 52 | 53 | } /*details*/ 54 | 55 | 56 | //---------------------------------------------------------------------------------------------- 57 | template 58 | inline auto make_parallel_for_task_group(_Scheduler &sc, const std::string &name, const _Partitioner &partitioner, task_priority prio, _Function &&func) 59 | { 60 | if (!partitioner.isValid()) 61 | { 62 | return decltype(make_parallel_group<_TaskType, _GroupContext>(sc, "", prio, 0))(nullptr); 63 | } 64 | 65 | const auto nbElements = partitioner.elementCount(); 66 | const auto nbBatches = partitioner.batchCount(); 67 | const auto &groupName = name + " (" + std::to_string(nbElements) + " items)"; 68 | auto spTaskGroup = make_parallel_group<_TaskType, _GroupContext>(sc, groupName, prio, nbBatches); 69 | auto spPartitioner = std::make_shared<_Partitioner>(partitioner); 70 | 71 | for (auto batchIndex = 0; batchIndex < nbBatches; ++batchIndex) 72 | { 73 | const auto &taskName = "Batch " + std::to_string(batchIndex + 1) + "/" + std::to_string(nbBatches); 74 | auto taskHandle = make_task(taskName, prio, 75 | [batchIndex, func, spPartitioner]() 76 | { 77 | int32_t first = 0; 78 | int32_t last = 0; 79 | while (spPartitioner->getNextValidRange(first, last)) 80 | { 81 | for (auto elementIndex = first; elementIndex != last; ++elementIndex) 82 | { 83 | details::parallel_for_call(func, batchIndex, elementIndex); 84 | } 85 | } 86 | }); 87 | 88 | spTaskGroup->addTask(std::move(taskHandle)); 89 | } 90 | 91 | return std::move(spTaskGroup); 92 | } 93 | //---------------------------------------------------------------------------------------------- 94 | 95 | 96 | //---------------------------------------------------------------------------------------------- 97 | template 98 | inline void parallel_for(_Scheduler &sc, const std::string &name, const _Partitioner &partitioner, task_priority prio, _Function &&func) 99 | { 100 | if (auto spTaskGroup = make_parallel_for_task_group(sc, name, partitioner, prio, std::forward<_Function>(func))) 101 | { 102 | sc.add(task_handle(spTaskGroup)).activeWait(); 103 | } 104 | } 105 | //---------------------------------------------------------------------------------------------- 106 | 107 | } /*oqpi*/ 108 | -------------------------------------------------------------------------------- /include/oqpi/parallel_algorithms/simple_partitioner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/parallel_algorithms/base_partitioner.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | // Partitioner dividing a set of indices into fixed size batches and giving one batch to each 12 | // worker. 13 | // 14 | class simple_partitioner 15 | : public base_partitioner 16 | { 17 | public: 18 | simple_partitioner(int32_t firstIndex, int32_t lastIndex, int32_t maxBatches) 19 | : base_partitioner (firstIndex, lastIndex, maxBatches) 20 | , nbElementsPerBatch_ ((elementCount_ >= maxBatches) ? (elementCount_ / batchCount_) : 1) 21 | , remainder_ ((elementCount_ >= maxBatches) ? (elementCount_ % batchCount_) : 0) 22 | , batchIndex_ (0) 23 | {} 24 | 25 | simple_partitioner(int32_t elementsCount, int32_t maxBatches) 26 | : simple_partitioner(0, elementsCount, maxBatches) 27 | {} 28 | 29 | simple_partitioner(const simple_partitioner &other) 30 | : base_partitioner (other) 31 | , nbElementsPerBatch_ (other.nbElementsPerBatch_) 32 | , remainder_ (other.remainder_) 33 | , batchIndex_ (other.batchIndex_.load()) 34 | {} 35 | 36 | inline bool getNextValidRange(int32_t &fromIndex, int32_t &toIndex) 37 | { 38 | const auto batchIndex = batchIndex_++; 39 | if (batchIndex >= batchCount_) 40 | { 41 | // All batches have been processed 42 | return false; 43 | } 44 | 45 | // Return the range [fromIndex; toIndex[ 46 | fromIndex = firstIndexOfBatch(batchIndex); 47 | toIndex = lastIndexOfBatch(batchIndex); 48 | 49 | return true; 50 | } 51 | 52 | private: 53 | inline int32_t firstIndexOfBatch(int32_t batchIndex) const 54 | { 55 | // The first index of the first batch is firstIndex_. 56 | // The first index of all other batches is the last index of the previous batch. 57 | return (batchIndex > 0) ? lastIndexOfBatch(batchIndex - 1) : firstIndex_; 58 | } 59 | 60 | inline int32_t lastIndexOfBatch(int32_t batchIndex) const 61 | { 62 | // If there's a remainder, each batch with batchIndex < remainder will process one extra element. 63 | // So each batch has to be offset by at most remainder_ number of elements. 64 | const auto offset = (batchIndex < remainder_) ? batchIndex + 1 : remainder_; 65 | return firstIndex_ + ((batchIndex + 1) * nbElementsPerBatch_) + offset; 66 | } 67 | 68 | private: 69 | // Minimum number of elements each batch should have (can be more if there is a remainder). 70 | const int32_t nbElementsPerBatch_; 71 | // If elementCount_ is not divisible by batchCount_, this holds the remainder of that division. 72 | const int32_t remainder_; 73 | // Each worker increments this atomic and is given the corresponding range, until it reaches batchCount_. 74 | std::atomic batchIndex_; 75 | }; 76 | 77 | } /*oqpi*/ 78 | -------------------------------------------------------------------------------- /include/oqpi/platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Define every supported platform to 0 so it can be used in #if statements 4 | #define OQPI_PLATFORM_WIN (0) 5 | #define OQPI_PLATFORM_POSIX (0) 6 | 7 | // Define only the current platform to 1 8 | #if defined(_WIN32) 9 | # undef OQPI_PLATFORM_WIN 10 | # define OQPI_PLATFORM_WIN (1) 11 | # ifndef WIN32_LEAN_AND_MEAN 12 | # define WIN32_LEAN_AND_MEAN 13 | # endif 14 | # ifndef VC_EXTRALEAN 15 | # define VC_EXTRALEAN 16 | # endif 17 | # ifndef NOMINMAX 18 | # define NOMINMAX 19 | # endif 20 | # include 21 | #elif defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) 22 | # undef OQPI_PLATFORM_POSIX 23 | # define OQPI_PLATFORM_POSIX (1) 24 | #endif 25 | -------------------------------------------------------------------------------- /include/oqpi/scheduling.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/scheduling_helpers.hpp" 4 | #include "oqpi/scheduling/task.hpp" 5 | #include "oqpi/scheduling/scheduler.hpp" 6 | #include "oqpi/scheduling/task_handle.hpp" 7 | #include "oqpi/scheduling/task_context.hpp" 8 | #include "oqpi/scheduling/group_context.hpp" 9 | #include "oqpi/scheduling/sequence_group.hpp" 10 | #include "oqpi/scheduling/parallel_group.hpp" 11 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/group_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/error_handling.hpp" 4 | #include "oqpi/scheduling/context_container.hpp" 5 | 6 | 7 | namespace oqpi { 8 | 9 | //---------------------------------------------------------------------------------------------- 10 | class task_group_base; 11 | class task_handle; 12 | //---------------------------------------------------------------------------------------------- 13 | 14 | //---------------------------------------------------------------------------------------------- 15 | // Optional base class for group contexts, should be inherited from virtually 16 | // 17 | class group_context_base 18 | { 19 | public: 20 | group_context_base(task_group_base *pOwner, std::string name) 21 | : pOwner_(pOwner) 22 | {} 23 | 24 | task_group_base* owner() const 25 | { 26 | oqpi_check(pOwner_); 27 | return pOwner_; 28 | } 29 | 30 | inline void onAddedToGroup(const task_group_sptr &) {}; 31 | inline void onTaskAdded(const task_handle &) {} 32 | inline void onPreExecute() {} 33 | inline void onPostExecute() {} 34 | 35 | private: 36 | task_group_base *pOwner_; 37 | }; 38 | //---------------------------------------------------------------------------------------------- 39 | 40 | //---------------------------------------------------------------------------------------------- 41 | template 42 | using group_context_container = context_container; 43 | //---------------------------------------------------------------------------------------------- 44 | using empty_group_context = group_context_container<>; 45 | //---------------------------------------------------------------------------------------------- 46 | 47 | } /*oqpi*/ 48 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/parallel_group.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "oqpi/scheduling/task_group.hpp" 7 | 8 | 9 | namespace oqpi { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | // Builds a fork of tasks as such: 13 | // 14 | // /----[T0]----\ 15 | // / ----[T1]---- \ 16 | // (fork) o--- ----[T2]---- ---o (join) 17 | // \ ---- .. ---- / 18 | // \----[Tn]----/ 19 | // 20 | // This group is NOT thread safe! Meaning it does not allow multiple threads to concurrently add 21 | // tasks to it. 22 | // 23 | template 24 | class parallel_group final 25 | : public task_group<_Scheduler, _TaskType, _GroupContext> 26 | { 27 | public: 28 | //------------------------------------------------------------------------------------------ 29 | parallel_group(_Scheduler &sc, const std::string &name, task_priority priority, int32_t taskCount = 0, int32_t maxSimultaneousTasks = 0) 30 | : task_group<_Scheduler, _TaskType, _GroupContext>(sc, name, priority) 31 | , activeTasksCount_(0) 32 | , maxSimultaneousTasks_(maxSimultaneousTasks) 33 | , currentTaskIndex_(1) 34 | { 35 | tasks_.reserve(taskCount); 36 | } 37 | 38 | public: 39 | //------------------------------------------------------------------------------------------ 40 | virtual bool empty() const override final 41 | { 42 | return tasks_.empty(); 43 | } 44 | 45 | //------------------------------------------------------------------------------------------ 46 | // For debug purposes 47 | virtual void executeSingleThreadedImpl() override final 48 | { 49 | if (task_base::tryGrab()) 50 | { 51 | const auto taskCount = tasks_.size(); 52 | if (oqpi_ensure(taskCount > 0)) 53 | { 54 | oqpi_check(taskCount == activeTasksCount_); 55 | for (size_t i = 0; i < taskCount; ++i) 56 | { 57 | tasks_[i].executeSingleThreaded(); 58 | } 59 | } 60 | tasks_.clear(); 61 | } 62 | } 63 | 64 | //------------------------------------------------------------------------------------------ 65 | virtual void activeWait() override final 66 | { 67 | for (auto &hTask : tasks_) 68 | { 69 | if (hTask.tryGrab()) 70 | { 71 | hTask.execute(); 72 | } 73 | } 74 | 75 | this->wait(); 76 | } 77 | 78 | protected: 79 | //------------------------------------------------------------------------------------------ 80 | virtual void addTaskImpl(const task_handle &hTask) override final 81 | { 82 | tasks_.emplace_back(hTask); 83 | activeTasksCount_.fetch_add(1); 84 | } 85 | 86 | //------------------------------------------------------------------------------------------ 87 | virtual void executeImpl() override final 88 | { 89 | const auto taskCount = tasks_.size(); 90 | if (oqpi_ensuref(taskCount > 0, "Trying to execute an empty group")) 91 | { 92 | size_t i = 0; 93 | int32_t scheduledTasks = 0; 94 | 95 | while ((i = currentTaskIndex_.fetch_add(1)) < taskCount) 96 | { 97 | if (!tasks_[i].isGrabbed() && !tasks_[i].isDone()) 98 | { 99 | this->scheduler_.add(tasks_[i]); 100 | if (maxSimultaneousTasks_ > 0 && ++scheduledTasks >= maxSimultaneousTasks_-1) 101 | { 102 | break; 103 | } 104 | } 105 | } 106 | 107 | if (tasks_[0].tryGrab()) 108 | { 109 | tasks_[0].execute(); 110 | } 111 | } 112 | } 113 | 114 | //------------------------------------------------------------------------------------------ 115 | virtual void oneTaskDone() override final 116 | { 117 | const auto previousTaskCount = activeTasksCount_.fetch_sub(1); 118 | if (previousTaskCount == 1) 119 | { 120 | this->notifyGroupDone(); 121 | } 122 | else if (maxSimultaneousTasks_ > 0) 123 | { 124 | const auto taskCount = tasks_.size(); 125 | size_t i = 0; 126 | while ((i = currentTaskIndex_.fetch_add(1)) < taskCount) 127 | { 128 | if (!tasks_[i].isGrabbed() && !tasks_[i].isDone()) 129 | { 130 | this->scheduler_.add(tasks_[i]); 131 | break; 132 | } 133 | } 134 | } 135 | } 136 | 137 | protected: 138 | // Number of tasks still running or yet to be run 139 | std::atomic activeTasksCount_; 140 | // Tasks of the fork 141 | std::vector tasks_; 142 | // Number of maximum tasks this group is allowed to run in parallel 143 | const int32_t maxSimultaneousTasks_; 144 | // Index of the next task to be scheduled 145 | std::atomic currentTaskIndex_; 146 | }; 147 | //---------------------------------------------------------------------------------------------- 148 | 149 | 150 | //---------------------------------------------------------------------------------------------- 151 | template 152 | inline auto make_parallel_group(_Scheduler &sc, const std::string &name, task_priority prio, int32_t taskCount = 0, int32_t maxSimultaneousTasks = 0) 153 | { 154 | return make_task_group(sc, name, prio, taskCount, maxSimultaneousTasks); 155 | } 156 | //---------------------------------------------------------------------------------------------- 157 | 158 | } /*oqpi*/ 159 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/sequence_group.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/scheduling/task_group.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | // Builds a sequence of tasks as such: 12 | // 13 | // [T0] -> [T1] -> [T2] -> ... -> [Tn-1] -> [Tn] 14 | // 15 | // [Tn] waits on [Tn-1] completion which waits on [Tn-2] completion etc... 16 | // 17 | // This group is not thread safe, meaning the user has to ensure thread safety herself when 18 | // adding tasks to this kind of group. 19 | // 20 | template 21 | class sequence_group final 22 | : public task_group<_Scheduler, _TaskType, _GroupContext> 23 | { 24 | public: 25 | //------------------------------------------------------------------------------------------ 26 | sequence_group(_Scheduler &sc, const std::string &name, task_priority priority) 27 | : task_group<_Scheduler, _TaskType, _GroupContext>(sc, name, priority) 28 | {} 29 | 30 | public: 31 | //------------------------------------------------------------------------------------------ 32 | virtual bool empty() const override final 33 | { 34 | return tasks_.empty(); 35 | } 36 | 37 | //------------------------------------------------------------------------------------------ 38 | // For debug purposes 39 | virtual void executeSingleThreadedImpl() override final 40 | { 41 | if (task_base::tryGrab()) 42 | { 43 | while (!empty()) 44 | { 45 | auto hTask = popTask(); 46 | hTask.executeSingleThreaded(); 47 | } 48 | } 49 | } 50 | 51 | 52 | protected: 53 | //------------------------------------------------------------------------------------------ 54 | virtual void addTaskImpl(const task_handle &hTask) override final 55 | { 56 | tasks_.push(hTask); 57 | } 58 | 59 | //------------------------------------------------------------------------------------------ 60 | // Executes the current task 61 | virtual void executeImpl() override final 62 | { 63 | auto hTask = popTask(); 64 | if (hTask.tryGrab()) 65 | { 66 | hTask.execute(); 67 | } 68 | } 69 | 70 | //------------------------------------------------------------------------------------------ 71 | virtual void oneTaskDone() override final 72 | { 73 | if (!empty()) 74 | { 75 | this->scheduler_.add(popTask()); 76 | } 77 | else 78 | { 79 | this->notifyGroupDone(); 80 | } 81 | } 82 | 83 | private: 84 | //------------------------------------------------------------------------------------------ 85 | task_handle popTask() 86 | { 87 | task_handle hTask; 88 | if (oqpi_ensuref(!empty(), "Attempting to execute an empty sequence: %d", this->getUID())) 89 | { 90 | hTask = tasks_.front(); 91 | tasks_.pop(); 92 | } 93 | return hTask; 94 | } 95 | 96 | private: 97 | // Tasks of the sequence 98 | std::queue tasks_; 99 | }; 100 | //---------------------------------------------------------------------------------------------- 101 | 102 | 103 | //---------------------------------------------------------------------------------------------- 104 | template 105 | inline auto make_sequence_group(_Scheduler &sc, const std::string &name, task_priority prio) 106 | { 107 | return make_task_group(sc, name, prio); 108 | } 109 | //---------------------------------------------------------------------------------------------- 110 | 111 | } /*oqpi*/ 112 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "oqpi/scheduling/task_base.hpp" 7 | #include "oqpi/scheduling/task_result.hpp" 8 | #include "oqpi/scheduling/task_notifier.hpp" 9 | 10 | 11 | namespace oqpi { 12 | 13 | template 14 | class task final 15 | : public task_base 16 | , public task_result> 17 | , public _TaskContext 18 | , public notifier<_TaskType, _EventType> 19 | { 20 | //------------------------------------------------------------------------------------------ 21 | using self_type = task<_TaskType, _EventType, _TaskContext, _Func>; 22 | using return_type = typename std::invoke_result_t<_Func>; 23 | using task_result_type = task_result; 24 | using notifier_type = notifier<_TaskType, _EventType>; 25 | 26 | public: 27 | //------------------------------------------------------------------------------------------ 28 | task(const std::string &name, task_priority priority, _Func func) 29 | : task_base(priority) 30 | , _TaskContext(this, name) 31 | , notifier_type() 32 | , func_(std::move(func)) 33 | {} 34 | 35 | //------------------------------------------------------------------------------------------ 36 | // Movable 37 | task(self_type &&other) 38 | : task_base(std::move(other)) 39 | , _TaskContext(std::move(other)) 40 | , notifier_type(std::move(other)) 41 | , func_(std::move(other.func_)) 42 | {} 43 | 44 | //------------------------------------------------------------------------------------------ 45 | self_type& operator =(self_type &&rhs) 46 | { 47 | if (this != &rhs) 48 | { 49 | task_base::operator =(std::move(rhs)); 50 | _TaskContext::operator =(std::move(rhs)); 51 | notifier_type::operator =(std::move(rhs)); 52 | func_ = std::move(rhs.func_); 53 | } 54 | return (*this); 55 | } 56 | 57 | //------------------------------------------------------------------------------------------ 58 | // Not copyable 59 | task(const self_type &) = delete; 60 | self_type& operator =(const self_type &) = delete; 61 | 62 | public: 63 | //------------------------------------------------------------------------------------------ 64 | virtual void execute() override final 65 | { 66 | if (oqpi_ensuref(task_base::isGrabbed(), "Trying to execute an ungrabbed task: %d", task_base::getUID())) 67 | { 68 | invoke(); 69 | task_base::notifyParent(); 70 | } 71 | } 72 | 73 | //------------------------------------------------------------------------------------------ 74 | virtual void executeSingleThreaded() override final 75 | { 76 | if (task_base::tryGrab()) 77 | { 78 | invoke(); 79 | // We are single threaded meaning that our parent (if any) is running this task 80 | // in its executeSingleThreaded function, so no need to notify it. 81 | } 82 | } 83 | 84 | //------------------------------------------------------------------------------------------ 85 | virtual void wait() override final 86 | { 87 | notifier_type::wait(); 88 | } 89 | 90 | //------------------------------------------------------------------------------------------ 91 | virtual void activeWait() override final 92 | { 93 | if (task_base::tryGrab()) 94 | { 95 | execute(); 96 | } 97 | else 98 | { 99 | wait(); 100 | } 101 | } 102 | 103 | //------------------------------------------------------------------------------------------ 104 | virtual void onParentGroupSet() override final 105 | { 106 | _TaskContext::task_onAddedToGroup(this->spParentGroup_); 107 | } 108 | 109 | //------------------------------------------------------------------------------------------ 110 | return_type getResult() const 111 | { 112 | oqpi_checkf(task_base::isDone(), "Trying to get the result of an unfinished task: %d", task_base::getUID()); 113 | return task_result_type::getResult(); 114 | } 115 | 116 | //------------------------------------------------------------------------------------------ 117 | return_type waitForResult() 118 | { 119 | wait(); 120 | return getResult(); 121 | } 122 | 123 | private: 124 | //------------------------------------------------------------------------------------------ 125 | inline void invoke() 126 | { 127 | // Run the preExecute code of the context 128 | _TaskContext::task_onPreExecute(); 129 | // Run the task itself 130 | task_result_type::run(func_); 131 | // Flag the task as done 132 | task_base::setDone(); 133 | // Run the postExecute code of the context 134 | _TaskContext::task_onPostExecute(); 135 | // Signal that the task is done 136 | notifier_type::notify(); 137 | } 138 | 139 | private: 140 | _Func func_; 141 | }; 142 | //---------------------------------------------------------------------------------------------- 143 | 144 | 145 | //---------------------------------------------------------------------------------------------- 146 | // Type : user defined 147 | // Context : user defined 148 | template 149 | inline auto make_task(const std::string &name, task_priority priority, _Func &&func, _Args &&...args) 150 | { 151 | auto f = [func = std::forward<_Func>(func), args = std::make_tuple(std::forward<_Args>(args)...)] () mutable 152 | { 153 | return std::apply(func, std::move(args)); 154 | }; 155 | 156 | using task_type = task<_TaskType, _EventType, _TaskContext, std::decay_t>; 157 | return std::make_shared 158 | ( 159 | name, 160 | priority, 161 | std::move(f) 162 | ); 163 | } 164 | //---------------------------------------------------------------------------------------------- 165 | 166 | } /*oqpi*/ 167 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "oqpi/scheduling/task_type.hpp" 7 | 8 | 9 | namespace oqpi { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | class task_group_base; 13 | //---------------------------------------------------------------------------------------------- 14 | class task_base; 15 | //---------------------------------------------------------------------------------------------- 16 | using task_group_sptr = std::shared_ptr; 17 | //---------------------------------------------------------------------------------------------- 18 | using task_uptr = std::unique_ptr; 19 | using task_sptr = std::shared_ptr; 20 | using task_wptr = std::weak_ptr; 21 | //---------------------------------------------------------------------------------------------- 22 | 23 | 24 | //---------------------------------------------------------------------------------------------- 25 | // Base class for all kind of tasks, unit tasks as well as groups 26 | class task_base 27 | { 28 | public: 29 | //------------------------------------------------------------------------------------------ 30 | // Constructor 31 | task_base(task_priority priority) 32 | : uid_(uid_provider()) 33 | , spParentGroup_(nullptr) 34 | , priority_(priority) 35 | , grabbed_(false) 36 | , done_(false) 37 | {} 38 | 39 | //------------------------------------------------------------------------------------------ 40 | // Used as a base class for the whole task hierarchy 41 | virtual ~task_base() = default; 42 | 43 | //------------------------------------------------------------------------------------------ 44 | // Can be moved 45 | task_base(task_base &&other) noexcept 46 | : uid_(other.uid_) 47 | , spParentGroup_(std::move(other.spParentGroup_)) 48 | , priority_(other.priority_) 49 | , grabbed_(other.grabbed_.load()) 50 | , done_(other.done_.load()) 51 | {} 52 | 53 | //------------------------------------------------------------------------------------------ 54 | task_base& operator =(task_base &&rhs) noexcept 55 | { 56 | if (this != &rhs) 57 | { 58 | uid_ = rhs.uid_; 59 | spParentGroup_ = std::move(rhs.spParentGroup_); 60 | priority_ = rhs.priority_; 61 | grabbed_ = rhs.grabbed_.load(); 62 | done_ = rhs.done_.load(); 63 | 64 | rhs.uid_ = invalid_task_uid; 65 | } 66 | return (*this); 67 | } 68 | 69 | //------------------------------------------------------------------------------------------ 70 | // Copy disabled 71 | task_base(const task_base &) = delete; 72 | task_base& operator =(const task_base &) = delete; 73 | 74 | public: 75 | //------------------------------------------------------------------------------------------ 76 | // Interface 77 | virtual void execute() = 0; 78 | virtual void executeSingleThreaded() = 0; 79 | virtual void wait() = 0; 80 | virtual void activeWait() = 0; 81 | 82 | protected: 83 | virtual void onParentGroupSet() = 0; 84 | 85 | public: 86 | //------------------------------------------------------------------------------------------ 87 | // Accessors 88 | inline task_uid getUID() const 89 | { 90 | return uid_; 91 | } 92 | 93 | inline void setParentGroup(const task_group_sptr &spParentGroup) 94 | { 95 | spParentGroup_ = spParentGroup; 96 | onParentGroupSet(); 97 | } 98 | 99 | inline const task_group_sptr& getParentGroup() const 100 | { 101 | return spParentGroup_; 102 | } 103 | 104 | inline task_priority getPriority() const 105 | { 106 | return priority_; 107 | } 108 | 109 | inline bool tryGrab() 110 | { 111 | bool expected = false; 112 | return grabbed_.compare_exchange_strong(expected, true); 113 | } 114 | 115 | inline bool isGrabbed() const 116 | { 117 | return grabbed_.load(); 118 | } 119 | 120 | inline bool isDone() const 121 | { 122 | return done_.load(); 123 | } 124 | 125 | inline void setDone() 126 | { 127 | done_.store(true); 128 | } 129 | 130 | inline void notifyParent(); 131 | 132 | protected: 133 | //------------------------------------------------------------------------------------------ 134 | // The unique id of this task 135 | task_uid uid_; 136 | // Optional parent group 137 | task_group_sptr spParentGroup_; 138 | // Relative priority of the task 139 | task_priority priority_; 140 | // Token that has to be acquired by anyone before executing the task 141 | std::atomic grabbed_; 142 | // Flag flipped once the task execution is done 143 | std::atomic done_; 144 | 145 | private: 146 | //------------------------------------------------------------------------------------------ 147 | static inline task_uid uid_provider() 148 | { 149 | // Starts at 1 as 0 is the invalid value 150 | static std::atomic uid_generator(1); 151 | return uid_generator.fetch_add(1); 152 | } 153 | }; 154 | 155 | } /*oqpi*/ 156 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/scheduling/context_container.hpp" 4 | #include "oqpi/scheduling/task_base.hpp" 5 | 6 | 7 | namespace oqpi { 8 | 9 | //---------------------------------------------------------------------------------------------- 10 | class task_base; 11 | //---------------------------------------------------------------------------------------------- 12 | 13 | //---------------------------------------------------------------------------------------------- 14 | // Optional base class for task contexts, should be inherited from virtually 15 | // 16 | class task_context_base 17 | { 18 | public: 19 | task_context_base(task_base *pOwner, const std::string &) 20 | : pOwner_(pOwner) 21 | {} 22 | 23 | public: 24 | inline task_base* owner() const { return pOwner_; } 25 | 26 | public: 27 | inline void onAddedToGroup(const task_group_sptr &) {} 28 | inline void onPreExecute() {} 29 | inline void onPostExecute() {} 30 | 31 | private: 32 | task_base *pOwner_; 33 | }; 34 | //---------------------------------------------------------------------------------------------- 35 | 36 | //---------------------------------------------------------------------------------------------- 37 | template 38 | using task_context_container = context_container; 39 | //---------------------------------------------------------------------------------------------- 40 | using empty_task_context = task_context_container<>; 41 | //---------------------------------------------------------------------------------------------- 42 | 43 | } /*oqpi*/ 44 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_group.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/scheduling/task_handle.hpp" 4 | #include "oqpi/scheduling/task_notifier.hpp" 5 | #include "oqpi/scheduling/task_group_base.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | template 12 | class task_group 13 | : public task_group_base 14 | , public _GroupContext 15 | , public notifier<_TaskType> 16 | { 17 | //------------------------------------------------------------------------------------------ 18 | using self_type = task_group<_Scheduler, _TaskType, _GroupContext>; 19 | using notifier_type = notifier<_TaskType>; 20 | 21 | public: 22 | //------------------------------------------------------------------------------------------ 23 | task_group(_Scheduler &sc, const std::string &name, task_priority priority) 24 | : task_group_base(priority) 25 | , _GroupContext(this, name) 26 | , notifier_type() 27 | , scheduler_(sc) 28 | {} 29 | 30 | //------------------------------------------------------------------------------------------ 31 | virtual ~task_group() = default; 32 | 33 | public: 34 | //------------------------------------------------------------------------------------------ 35 | // task_group_base implemented interface 36 | virtual void addTask(task_handle hTask) override final 37 | { 38 | if(hTask.isValid()) 39 | { 40 | if (oqpi_ensuref(hTask.getParentGroup() == nullptr, 41 | "This task (%d) is already bound to a group: %d", hTask.getUID(), hTask.getParentGroup()->getUID())) 42 | { 43 | hTask.setParentGroup(shared_from_this()); 44 | addTaskImpl(hTask); 45 | _GroupContext::group_onTaskAdded(hTask); 46 | } 47 | } 48 | } 49 | 50 | //------------------------------------------------------------------------------------------ 51 | virtual void execute() override final 52 | { 53 | _GroupContext::group_onPreExecute(); 54 | executeImpl(); 55 | } 56 | 57 | //------------------------------------------------------------------------------------------ 58 | virtual void executeSingleThreaded() override final 59 | { 60 | _GroupContext::group_onPreExecute(); 61 | executeSingleThreadedImpl(); 62 | _GroupContext::group_onPostExecute(); 63 | 64 | task_base::setDone(); 65 | notifier_type::notify(); 66 | } 67 | 68 | //------------------------------------------------------------------------------------------ 69 | virtual void wait() override final 70 | { 71 | notifier_type::wait(); 72 | } 73 | 74 | //------------------------------------------------------------------------------------------ 75 | virtual void activeWait() override 76 | { 77 | oqpi_checkf(false, "Not supported, fall back to wait"); 78 | wait(); 79 | } 80 | 81 | //------------------------------------------------------------------------------------------ 82 | virtual void onParentGroupSet() override final 83 | { 84 | _GroupContext::group_onAddedToGroup(this->spParentGroup_); 85 | } 86 | 87 | protected: 88 | //------------------------------------------------------------------------------------------ 89 | // Implementation details interface 90 | virtual void addTaskImpl(const task_handle &hTask) = 0; 91 | virtual void executeImpl() = 0; 92 | virtual void executeSingleThreadedImpl() = 0; 93 | 94 | protected: 95 | //------------------------------------------------------------------------------------------ 96 | // Called once all tasks of a group are done 97 | void notifyGroupDone() 98 | { 99 | task_base::setDone(); 100 | _GroupContext::group_onPostExecute(); 101 | notifier_type::notify(); 102 | task_base::notifyParent(); 103 | } 104 | 105 | protected: 106 | // We need a reference to the scheduler so that the groups can add their tasks 107 | _Scheduler &scheduler_; 108 | }; 109 | //---------------------------------------------------------------------------------------------- 110 | 111 | 112 | //---------------------------------------------------------------------------------------------- 113 | template class _TaskGroupType, task_type _TaskType, typename _GroupContext, typename _Scheduler, typename... _Args> 114 | inline auto make_task_group(_Scheduler &sc, const std::string &name, _Args &&...args) 115 | { 116 | return std::make_shared<_TaskGroupType<_Scheduler, _TaskType, _GroupContext>>(sc, name, std::forward<_Args>(args)...); 117 | } 118 | //---------------------------------------------------------------------------------------------- 119 | 120 | } /*oqpi*/ 121 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_group_base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "oqpi/scheduling/task_base.hpp" 5 | 6 | 7 | namespace oqpi { 8 | 9 | //---------------------------------------------------------------------------------------------- 10 | class task_handle; 11 | //---------------------------------------------------------------------------------------------- 12 | class task_group_base; 13 | //---------------------------------------------------------------------------------------------- 14 | using task_group_sptr = std::shared_ptr; 15 | using task_group_wptr = std::weak_ptr; 16 | //---------------------------------------------------------------------------------------------- 17 | 18 | 19 | //---------------------------------------------------------------------------------------------- 20 | class task_group_base 21 | : public task_base 22 | , public std::enable_shared_from_this 23 | { 24 | public: 25 | task_group_base(task_priority priority) 26 | : task_base(priority) 27 | {} 28 | 29 | virtual ~task_group_base() = default; 30 | 31 | public: 32 | virtual void addTask(task_handle hTask) = 0; 33 | virtual void oneTaskDone() = 0; 34 | virtual bool empty() const = 0; 35 | }; 36 | //---------------------------------------------------------------------------------------------- 37 | 38 | 39 | //---------------------------------------------------------------------------------------------- 40 | // Declared here as a workaround to the circular dependency between task_base and 41 | // task_group_base. 42 | inline void task_base::notifyParent() 43 | { 44 | if (spParentGroup_) 45 | { 46 | spParentGroup_->oneTaskDone(); 47 | spParentGroup_.reset(); 48 | } 49 | } 50 | //---------------------------------------------------------------------------------------------- 51 | 52 | } /*oqpi*/ 53 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_handle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/error_handling.hpp" 4 | #include "oqpi/scheduling/task.hpp" 5 | #include "oqpi/scheduling/task_base.hpp" 6 | #include "oqpi/scheduling/task_type.hpp" 7 | 8 | 9 | namespace oqpi { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | class task_handle 13 | { 14 | public: 15 | //------------------------------------------------------------------------------------------ 16 | // Constructs an invalid handle 17 | task_handle() 18 | : spTask_(nullptr) 19 | {} 20 | 21 | //------------------------------------------------------------------------------------------ 22 | // Construct a valid handle by taking a shared pointer to a task_base 23 | task_handle(task_sptr spTask) 24 | : spTask_(std::move(spTask)) 25 | {} 26 | 27 | //------------------------------------------------------------------------------------------ 28 | // Construct a valid handle by taking a shared pointer to a task 29 | template 30 | task_handle(std::shared_ptr> spTask) 31 | : spTask_(std::move(spTask)) 32 | {} 33 | 34 | //------------------------------------------------------------------------------------------ 35 | // Movable 36 | task_handle(task_handle &&rhs) noexcept 37 | : spTask_(std::move(rhs.spTask_)) 38 | {} 39 | 40 | task_handle& operator =(task_handle &&rhs) noexcept 41 | { 42 | if (this != &rhs) 43 | { 44 | spTask_ = std::move(rhs.spTask_); 45 | } 46 | return (*this); 47 | } 48 | 49 | //------------------------------------------------------------------------------------------ 50 | // Copyable as well 51 | task_handle(const task_handle &rhs) 52 | : spTask_(rhs.spTask_) 53 | {} 54 | 55 | task_handle& operator =(const task_handle &rhs) 56 | { 57 | if (this != &rhs) 58 | { 59 | spTask_ = rhs.spTask_; 60 | } 61 | return (*this); 62 | } 63 | 64 | public: 65 | //------------------------------------------------------------------------------------------ 66 | bool isValid() const 67 | { 68 | return spTask_ != nullptr; 69 | } 70 | 71 | //------------------------------------------------------------------------------------------ 72 | void reset() 73 | { 74 | spTask_.reset(); 75 | } 76 | 77 | //------------------------------------------------------------------------------------------ 78 | void execute() 79 | { 80 | validate(); 81 | oqpi_checkf(spTask_->isGrabbed(), "Trying to execute an ungrabbed task: %d", getUID()); 82 | spTask_->execute(); 83 | } 84 | 85 | //------------------------------------------------------------------------------------------ 86 | void executeSingleThreaded() 87 | { 88 | validate(); 89 | spTask_->executeSingleThreaded(); 90 | } 91 | 92 | //------------------------------------------------------------------------------------------ 93 | void wait() const 94 | { 95 | validate(); 96 | spTask_->wait(); 97 | } 98 | 99 | //------------------------------------------------------------------------------------------ 100 | void activeWait() 101 | { 102 | validate(); 103 | spTask_->activeWait(); 104 | } 105 | 106 | //------------------------------------------------------------------------------------------ 107 | bool isDone() const 108 | { 109 | validate(); 110 | return spTask_->isDone(); 111 | } 112 | 113 | //------------------------------------------------------------------------------------------ 114 | bool tryGrab() 115 | { 116 | validate(); 117 | return spTask_->tryGrab(); 118 | } 119 | 120 | //------------------------------------------------------------------------------------------ 121 | bool isGrabbed() const 122 | { 123 | validate(); 124 | return spTask_->isGrabbed(); 125 | } 126 | 127 | //------------------------------------------------------------------------------------------ 128 | task_priority getPriority() const 129 | { 130 | validate(); 131 | return spTask_->getPriority(); 132 | } 133 | 134 | //------------------------------------------------------------------------------------------ 135 | void setParentGroup(const task_group_sptr &spParentGroup) 136 | { 137 | validate(); 138 | spTask_->setParentGroup(spParentGroup); 139 | } 140 | 141 | //------------------------------------------------------------------------------------------ 142 | const task_group_sptr& getParentGroup() const 143 | { 144 | validate(); 145 | return spTask_->getParentGroup(); 146 | } 147 | 148 | //------------------------------------------------------------------------------------------ 149 | uint64_t getUID() const 150 | { 151 | validate(); 152 | return spTask_->getUID(); 153 | } 154 | 155 | private: 156 | //------------------------------------------------------------------------------------------ 157 | void validate() const 158 | { 159 | oqpi_checkf(isValid(), "Invalid task handle."); 160 | } 161 | 162 | private: 163 | task_sptr spTask_; 164 | }; 165 | //---------------------------------------------------------------------------------------------- 166 | 167 | } /*oqpi*/ 168 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_notifier.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/synchronization/event.hpp" 4 | #include "oqpi/scheduling/task_type.hpp" 5 | 6 | 7 | namespace oqpi { 8 | 9 | //---------------------------------------------------------------------------------------------- 10 | // This class is specialized for each task type. 11 | // It provides 2 functions: wait() and notify(). 12 | // Inheriting from the right specialization will ensure that only waitable tasks instantiate 13 | // a synchronization object. 14 | template> 15 | class notifier; 16 | //---------------------------------------------------------------------------------------------- 17 | 18 | 19 | //---------------------------------------------------------------------------------------------- 20 | // Nothing to notify or wait for, for a fire_and_forget task 21 | template 22 | class notifier 23 | { 24 | protected: 25 | //------------------------------------------------------------------------------------------ 26 | notifier() {}; 27 | 28 | //------------------------------------------------------------------------------------------ 29 | void wait() { oqpi_checkf(false, "Can't wait on a fire_and_forget task"); } 30 | void notify() {} 31 | }; 32 | //---------------------------------------------------------------------------------------------- 33 | 34 | 35 | //---------------------------------------------------------------------------------------------- 36 | // Waitable tasks have a manual reset event to notify/wait on 37 | template 38 | class notifier 39 | { 40 | //------------------------------------------------------------------------------------------ 41 | using self_type = notifier; 42 | 43 | protected: 44 | //------------------------------------------------------------------------------------------ 45 | notifier() 46 | : event_() 47 | {} 48 | 49 | //------------------------------------------------------------------------------------------ 50 | // Movable 51 | notifier(self_type &&other) 52 | : event_(std::move(other.event_)) 53 | {} 54 | //------------------------------------------------------------------------------------------ 55 | self_type& operator =(self_type &&rhs) 56 | { 57 | if (this != &rhs) 58 | { 59 | event_ = std::move(rhs.event_); 60 | } 61 | return (*this); 62 | } 63 | 64 | //------------------------------------------------------------------------------------------ 65 | void wait() { event_.wait(); } 66 | void notify() { event_.notify(); } 67 | 68 | private: 69 | //------------------------------------------------------------------------------------------ 70 | notifier(const self_type &rhs) = delete; 71 | self_type& operator =(const self_type &rhs) = delete; 72 | 73 | private: 74 | // Done event 75 | _EventType event_; 76 | }; 77 | //---------------------------------------------------------------------------------------------- 78 | 79 | } /*oqpi*/ 80 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_result.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace oqpi { 5 | 6 | //---------------------------------------------------------------------------------------------- 7 | // Helper class containing an arbitrary result of a task. 8 | // _ReturnType must be default constructible and copyable or movable. 9 | template 10 | class task_result 11 | { 12 | protected: 13 | task_result() = default; 14 | 15 | protected: 16 | template 17 | void run(_Func &&f) 18 | { 19 | result_ = f(); 20 | } 21 | 22 | _ReturnType getResult() const 23 | { 24 | return result_; 25 | } 26 | 27 | protected: 28 | _ReturnType result_; 29 | }; 30 | //---------------------------------------------------------------------------------------------- 31 | 32 | 33 | //---------------------------------------------------------------------------------------------- 34 | // Specialization for tasks returning void 35 | template<> 36 | class task_result 37 | { 38 | protected: 39 | template 40 | void run(_Func &&f) 41 | { 42 | f(); 43 | } 44 | 45 | void getResult() const {} 46 | }; 47 | //---------------------------------------------------------------------------------------------- 48 | 49 | } /*oqpi*/ 50 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/task_type.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace oqpi { 4 | 5 | //---------------------------------------------------------------------------------------------- 6 | using task_uid = uint64_t; 7 | enum { invalid_task_uid = task_uid(0) }; 8 | //---------------------------------------------------------------------------------------------- 9 | 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | enum class task_priority : uint8_t 13 | { 14 | high = 0, 15 | above_normal, 16 | normal, 17 | below_normal, 18 | low, 19 | count, 20 | 21 | inherit 22 | }; 23 | //---------------------------------------------------------------------------------------------- 24 | 25 | //---------------------------------------------------------------------------------------------- 26 | enum class task_type 27 | { 28 | fire_and_forget, 29 | waitable 30 | }; 31 | //---------------------------------------------------------------------------------------------- 32 | 33 | } /*oqpi*/ 34 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/worker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/scheduling/worker_base.hpp" 4 | 5 | 6 | namespace oqpi { 7 | 8 | //---------------------------------------------------------------------------------------------- 9 | template 10 | class worker 11 | : public worker_base 12 | , public _WorkerContext 13 | { 14 | public: 15 | //------------------------------------------------------------------------------------------ 16 | worker(_Scheduler &sc, int32_t id, const worker_config &config) 17 | : worker_base(id, config) 18 | , _WorkerContext(this) 19 | , scheduler_(sc) 20 | , notifier_() 21 | , running_(false) 22 | {} 23 | 24 | //------------------------------------------------------------------------------------------ 25 | virtual ~worker() 26 | { 27 | stop(); 28 | } 29 | 30 | private: 31 | //------------------------------------------------------------------------------------------ 32 | virtual void start() override final 33 | { 34 | // Important to set the worker as running before starting the thread 35 | running_.store(true); 36 | 37 | // Add the id to the worker name so we can differentiate them when several workers 38 | // share the same config (and thus the same name) 39 | auto threadAttributes = worker_base::config_.threadAttributes; 40 | if (id_ >= 0) 41 | { 42 | threadAttributes.name_ += std::to_string(id_); 43 | } 44 | 45 | // Start the thread, running_ must be set to true beforehand 46 | thread_ = _Thread(threadAttributes, [this]() { run(); }); 47 | } 48 | 49 | //------------------------------------------------------------------------------------------ 50 | // Tags the worker as not running, note that this won't wake up the worker if it's asleep, 51 | // it's the caller's responsibility to wake it up 52 | virtual void stop() override final 53 | { 54 | running_.store(false); 55 | } 56 | 57 | //------------------------------------------------------------------------------------------ 58 | // If the thread hasn't been detached, this function will block until the worker stops 59 | virtual void join() override final 60 | { 61 | if (thread_.joinable()) 62 | { 63 | thread_.join(); 64 | } 65 | } 66 | 67 | //------------------------------------------------------------------------------------------ 68 | bool isRunning() const 69 | { 70 | return running_.load(); 71 | } 72 | 73 | //------------------------------------------------------------------------------------------ 74 | virtual void wait() override final 75 | { 76 | notifier_.wait(); 77 | } 78 | 79 | //------------------------------------------------------------------------------------------ 80 | virtual bool tryWait() override final 81 | { 82 | return notifier_.tryWait(); 83 | } 84 | 85 | //------------------------------------------------------------------------------------------ 86 | virtual void notify() override final 87 | { 88 | notifier_.notifyOne(); 89 | } 90 | 91 | //------------------------------------------------------------------------------------------ 92 | virtual void run() override final 93 | { 94 | // Inform the context that we're starting the worker thread 95 | _WorkerContext::worker_onStart(); 96 | 97 | // This is the worker's main loop 98 | while (isRunning()) 99 | { 100 | // Inform the context that we're potentially going idle while waiting for a task to work on 101 | _WorkerContext::worker_onIdle(); 102 | // Signal to the scheduler that we want a task to work on 103 | scheduler_.signalAvailableWorker(*this); 104 | // At this point we either have a task to work on or we've been waken up to quit the thread 105 | oqpi_check(!worker_base::isAvailable() || !isRunning()); 106 | // We consider ourselves active either way 107 | _WorkerContext::worker_onActive(); 108 | 109 | // Check if we've waken up to work on a new task 110 | if (isRunning()) 111 | { 112 | if (oqpi_ensure(worker_base::hTask_.isValid())) 113 | { 114 | // Inform the context that we're about to start the execution of a new task 115 | _WorkerContext::worker_onPreExecute(worker_base::hTask_); 116 | // Actually execute the task 117 | worker_base::hTask_.execute(); 118 | // Inform the context that we just finished the execution of a task 119 | _WorkerContext::worker_onPostExecute(worker_base::hTask_); 120 | // Reset the task, can potentially free the memory if there's no more reference to that task 121 | worker_base::hTask_.reset(); 122 | } 123 | } 124 | } 125 | 126 | // Just to make sure the memory is released (will happen in the destructor of the worker anyway) 127 | worker_base::hTask_.reset(); 128 | 129 | // Inform the context that we're stopping the worker thread 130 | _WorkerContext::worker_onStop(); 131 | } 132 | 133 | private: 134 | //------------------------------------------------------------------------------------------ 135 | // Reference to the parent scheduler, used to call signalAvailableWorker 136 | _Scheduler &scheduler_; 137 | // The underlying thread 138 | _Thread thread_; 139 | // Notifier used to signal/put to sleep the thread 140 | _Notifier notifier_; 141 | // Whether or not the worker is up and running 142 | std::atomic running_; 143 | }; 144 | //---------------------------------------------------------------------------------------------- 145 | 146 | } /*oqpi*/ 147 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/worker_base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "oqpi/scheduling/task_type.hpp" 8 | #include "oqpi/scheduling/task_handle.hpp" 9 | #include "oqpi/threading/thread_attributes.hpp" 10 | 11 | 12 | namespace oqpi { 13 | 14 | //---------------------------------------------------------------------------------------------- 15 | class worker_base; 16 | //---------------------------------------------------------------------------------------------- 17 | using worker_uptr = std::unique_ptr; 18 | //---------------------------------------------------------------------------------------------- 19 | 20 | 21 | //---------------------------------------------------------------------------------------------- 22 | // Worker priorities, each worker can be assigned to one or several priorities. 23 | // This enum is a bitfield coupled with task priorities. 24 | enum class worker_priority 25 | { 26 | wprio_high = 1 << int(task_priority::high), 27 | wprio_above_normal = 1 << int(task_priority::above_normal), 28 | wprio_normal = 1 << int(task_priority::normal), 29 | wprio_below_normal = 1 << int(task_priority::below_normal), 30 | wprio_low = 1 << int(task_priority::low), 31 | 32 | wprio_any_normal = wprio_above_normal | wprio_normal | wprio_below_normal, 33 | 34 | wprio_normal_or_low = wprio_low | wprio_any_normal, 35 | wprio_normal_or_high = wprio_any_normal | wprio_high, 36 | 37 | wprio_any = wprio_high | wprio_any_normal | wprio_low, 38 | }; 39 | //---------------------------------------------------------------------------------------------- 40 | 41 | 42 | //---------------------------------------------------------------------------------------------- 43 | // A config used to create one or several workers when registering to the scheduler 44 | struct worker_config 45 | { 46 | worker_config() 47 | : threadAttributes("oqpi::worker") 48 | , workerPrio(worker_priority::wprio_any) 49 | , count(1) 50 | {} 51 | 52 | thread_attributes threadAttributes; 53 | worker_priority workerPrio; 54 | int32_t count; 55 | }; 56 | //---------------------------------------------------------------------------------------------- 57 | 58 | 59 | //---------------------------------------------------------------------------------------------- 60 | // Whether or not a worker priority is compatible with a task priority 61 | inline bool can_work_on_priority(worker_priority workerPriority, task_priority taskPriority) 62 | { 63 | return ((1 << int(taskPriority)) & int(workerPriority)) != 0; 64 | } 65 | //---------------------------------------------------------------------------------------------- 66 | 67 | 68 | //---------------------------------------------------------------------------------------------- 69 | // Base class for workers, it's basically a wrapper around a thread with a notification 70 | // object to be able to wake it up/put it asleep. 71 | class worker_base 72 | { 73 | public: 74 | //------------------------------------------------------------------------------------------ 75 | worker_base(int id, const worker_config &config) 76 | : id_(config.count > 1 ? id : -1) 77 | , config_(config) 78 | {} 79 | 80 | //------------------------------------------------------------------------------------------ 81 | virtual ~worker_base() 82 | {} 83 | 84 | public: 85 | //------------------------------------------------------------------------------------------ 86 | bool isAvailable() const 87 | { 88 | return !hTask_.isValid(); 89 | } 90 | 91 | //------------------------------------------------------------------------------------------ 92 | void assign(task_handle &&hTask) 93 | { 94 | oqpi_checkf(isAvailable(), "Trying to assign a new task (%d) to a busy worker: %s (%d)", 95 | hTask.getUID(), config_.threadAttributes.name_.c_str(), hTask_.getUID()); 96 | 97 | hTask_ = std::move(hTask); 98 | } 99 | 100 | //------------------------------------------------------------------------------------------ 101 | worker_priority getPriority() const 102 | { 103 | return config_.workerPrio; 104 | } 105 | 106 | //------------------------------------------------------------------------------------------ 107 | bool canWorkOnPriority(task_priority taskPriority) const 108 | { 109 | return can_work_on_priority(getPriority(), taskPriority); 110 | } 111 | 112 | //------------------------------------------------------------------------------------------ 113 | int getId() const 114 | { 115 | return id_; 116 | } 117 | 118 | //------------------------------------------------------------------------------------------ 119 | const worker_config& getConfig() const 120 | { 121 | return config_; 122 | } 123 | 124 | //------------------------------------------------------------------------------------------ 125 | std::string getName() const 126 | { 127 | return id_ >= 0 128 | ? config_.threadAttributes.name_ + std::to_string(id_) 129 | : config_.threadAttributes.name_; 130 | } 131 | 132 | public: 133 | //------------------------------------------------------------------------------------------ 134 | virtual void start() = 0; 135 | //------------------------------------------------------------------------------------------ 136 | virtual void stop() = 0; 137 | //------------------------------------------------------------------------------------------ 138 | virtual void join() = 0; 139 | //------------------------------------------------------------------------------------------ 140 | virtual void wait() = 0; 141 | //------------------------------------------------------------------------------------------ 142 | virtual bool tryWait() = 0; 143 | //------------------------------------------------------------------------------------------ 144 | virtual void notify() = 0; 145 | 146 | protected: 147 | //------------------------------------------------------------------------------------------ 148 | // Main function of the thread, see worker.h for the implementation 149 | virtual void run() = 0; 150 | 151 | private: 152 | //------------------------------------------------------------------------------------------ 153 | // Not copyable 154 | worker_base(const worker_base &) = delete; 155 | worker_base& operator =(const worker_base &) = delete; 156 | 157 | protected: 158 | // Id of this worker, useful when a config is shared between several workers 159 | const int id_; 160 | // The config used to create this thread 161 | const worker_config config_; 162 | // The task the thread is currently working on, or an invalid handle if the worker is idle 163 | task_handle hTask_; 164 | }; 165 | //---------------------------------------------------------------------------------------------- 166 | 167 | } /*oqpi*/ 168 | -------------------------------------------------------------------------------- /include/oqpi/scheduling/worker_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/scheduling/context_container.hpp" 4 | 5 | 6 | namespace oqpi { 7 | 8 | //---------------------------------------------------------------------------------------------- 9 | class worker_base; 10 | class task_handle; 11 | //---------------------------------------------------------------------------------------------- 12 | 13 | //---------------------------------------------------------------------------------------------- 14 | // Optional base class for worker contexts, should be inherited from virtually 15 | // 16 | class worker_context_base 17 | { 18 | public: 19 | worker_context_base(worker_base *pOwner) 20 | : pOwner_(pOwner) 21 | {} 22 | 23 | public: 24 | inline worker_base* owner() const { return pOwner_; } 25 | 26 | public: 27 | inline void onStart() {} 28 | inline void onStop() {} 29 | inline void onIdle() {} 30 | inline void onActive() {} 31 | inline void onPreExecute(const task_handle &) {} 32 | inline void onPostExecute(const task_handle &) {} 33 | 34 | private: 35 | worker_base *pOwner_; 36 | }; 37 | //---------------------------------------------------------------------------------------------- 38 | 39 | //---------------------------------------------------------------------------------------------- 40 | template 41 | using worker_context_container = context_container; 42 | //---------------------------------------------------------------------------------------------- 43 | using empty_worker_context = worker_context_container<>; 44 | //---------------------------------------------------------------------------------------------- 45 | 46 | } /*oqpi*/ 47 | -------------------------------------------------------------------------------- /include/oqpi/synchronization.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/synchronization/sync.hpp" 4 | #include "oqpi/synchronization/event.hpp" 5 | #include "oqpi/synchronization/mutex.hpp" 6 | #include "oqpi/synchronization/semaphore.hpp" 7 | //#include "oqpi/synchronization/reader_writer_lock.hpp" 8 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/event.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | 5 | // Interface 6 | #include "oqpi/synchronization/interface/interface_event.hpp" 7 | // Platform specific implementations 8 | #if OQPI_PLATFORM_WIN 9 | # include "oqpi/synchronization/win/win_event.hpp" 10 | #elif OQPI_PLATFORM_POSIX 11 | # include "oqpi/synchronization/posix/posix_event.hpp" 12 | #else 13 | # error No event implementation defined for the current platform 14 | #endif 15 | 16 | namespace oqpi { 17 | 18 | //---------------------------------------------------------------------------------------------- 19 | template typename _Layer = local_sync_object> 20 | using auto_reset_event_interface = itfc::event, _Layer>; 21 | 22 | //---------------------------------------------------------------------------------------------- 23 | template typename _Layer = local_sync_object> 24 | using manual_reset_event_interface = itfc::event, _Layer>; 25 | 26 | 27 | #ifdef OQPI_USE_DEFAULT 28 | //---------------------------------------------------------------------------------------------- 29 | using auto_reset_event = auto_reset_event_interface<>; 30 | using manual_reset_event = manual_reset_event_interface<>; 31 | //---------------------------------------------------------------------------------------------- 32 | using global_auto_reset_event = auto_reset_event_interface; 33 | using global_manual_reset_event = manual_reset_event_interface; 34 | #endif 35 | 36 | } /*oqpi*/ 37 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/interface/interface_event.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/empty_layer.hpp" 6 | #include "oqpi/synchronization/sync_common.hpp" 7 | 8 | 9 | namespace oqpi { namespace itfc { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | template 13 | < 14 | // Platform specific implementation for events 15 | typename _Impl 16 | // Augmentation layer, needs to be templated and inherit from the implementation 17 | , template typename _Layer 18 | > 19 | class event 20 | : public std::conditional::value, _Impl, _Layer<_Impl>>::type 21 | { 22 | public: 23 | //------------------------------------------------------------------------------------------ 24 | // Whether the event has augmented layer(s) or not 25 | static constexpr auto is_lean = is_empty_layer<_Layer>::value; 26 | // The platform specific implementation 27 | using event_impl = _Impl; 28 | // The actual base type taking into account the presence or absence of augmentation layer(s) 29 | using base_type = std::conditional_t>; 30 | // The actual type 31 | using self_type = event; 32 | // Native handle 33 | using native_handle_type = typename event_impl::native_handle_type; 34 | 35 | public: 36 | //------------------------------------------------------------------------------------------ 37 | event() 38 | : base_type() 39 | {} 40 | 41 | //------------------------------------------------------------------------------------------ 42 | explicit event(const std::string &name) 43 | : base_type(name, sync_object_creation_options::open_or_create) 44 | {} 45 | 46 | //------------------------------------------------------------------------------------------ 47 | explicit event(const std::string &name, sync_object_creation_options creationOption) 48 | : base_type(name, creationOption) 49 | {} 50 | 51 | public: 52 | //------------------------------------------------------------------------------------------ 53 | // Movable 54 | event(self_type &&rhs) 55 | : base_type(std::move(*(base_type *)&rhs)) 56 | {} 57 | 58 | //------------------------------------------------------------------------------------------ 59 | inline self_type& operator =(self_type &&rhs) 60 | { 61 | if (this != &rhs) 62 | { 63 | base_type::operator =(std::move(rhs)); 64 | } 65 | return (*this); 66 | } 67 | 68 | //------------------------------------------------------------------------------------------ 69 | // Not copyable 70 | event(const self_type &) = delete; 71 | self_type& operator =(const self_type &) = delete; 72 | 73 | public: 74 | //------------------------------------------------------------------------------------------ 75 | // User interface 76 | native_handle_type getNativeHandle() const { return base_type::getNativeHandle(); } 77 | inline bool isValid() const { return base_type::isValid(); } 78 | inline void notify() { return base_type::notify(); } 79 | inline bool wait() { return base_type::wait(); } 80 | inline void reset() { return base_type::reset(); } 81 | inline void close() { return base_type::close(); } 82 | 83 | //------------------------------------------------------------------------------------------ 84 | template 85 | inline bool waitFor(const std::chrono::duration<_Rep, _Period>& relTime) 86 | { 87 | return base_type::waitFor(relTime); 88 | } 89 | template 90 | inline bool waitUntil(const std::chrono::time_point<_Clock, _Duration>& absTime) 91 | { 92 | return waitFor(absTime - _Clock::now()); 93 | } 94 | }; 95 | //---------------------------------------------------------------------------------------------- 96 | 97 | } /*itfc*/ } /*oqpi*/ 98 | 99 | 100 | namespace oqpi { 101 | 102 | //---------------------------------------------------------------------------------------------- 103 | struct event_auto_reset_policy_impl 104 | { 105 | static bool is_manual_reset_enabled() 106 | { 107 | return false; 108 | } 109 | 110 | // CLANG/LLVM will evaluate a static_assert(false) even if the function is never called 111 | // So we must trick it to not evaluate it by templating the assert condition 112 | template 113 | void reset() 114 | { 115 | static_assert(_UndefinedFunction, "reset() is not implemented for this event " 116 | "configuration, use oqpi::event instead of oqpi::auto_reset_event " 117 | "if you want to manually reset the event"); 118 | } 119 | }; 120 | //---------------------------------------------------------------------------------------------- 121 | 122 | } /*oqpi*/ 123 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/interface/interface_mutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/empty_layer.hpp" 6 | #include "oqpi/synchronization/sync_common.hpp" 7 | 8 | 9 | namespace oqpi { namespace itfc { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | template 13 | < 14 | // Platform specific implementation for mutex 15 | typename _Impl 16 | // Augmentation layer, needs to be templated and inherit from the implementation 17 | , template typename _Layer 18 | > 19 | class mutex 20 | : public std::conditional::value, _Impl, _Layer<_Impl>>::type 21 | { 22 | public: 23 | //------------------------------------------------------------------------------------------ 24 | // Whether the object has augmented layer(s) or not 25 | static constexpr auto is_lean = is_empty_layer<_Layer>::value; 26 | // The platform specific implementation 27 | using mutex_impl = _Impl; 28 | // The actual base type taking into account the presence or absence of augmentation layer(s) 29 | using base_type = typename std::conditional_t>; 30 | // The actual type 31 | using self_type = mutex; 32 | // Native handle 33 | using native_handle_type = typename mutex_impl::native_handle_type; 34 | 35 | public: 36 | //------------------------------------------------------------------------------------------ 37 | mutex() 38 | : base_type() 39 | {} 40 | 41 | //------------------------------------------------------------------------------------------ 42 | explicit mutex(const std::string &name) 43 | : base_type(name, sync_object_creation_options::open_or_create, true) 44 | {} 45 | 46 | //------------------------------------------------------------------------------------------ 47 | mutex(const std::string &name, sync_object_creation_options creationOption, bool lockOnCreation = true) 48 | : base_type(name, creationOption, lockOnCreation) 49 | {} 50 | 51 | public: 52 | //------------------------------------------------------------------------------------------ 53 | // Movable 54 | mutex(self_type &&rhs) 55 | : base_type(std::move(*(base_type *)&rhs)) 56 | {} 57 | 58 | //------------------------------------------------------------------------------------------ 59 | inline self_type& operator =(self_type &&rhs) 60 | { 61 | if (this != &rhs) 62 | { 63 | base_type::operator =(std::move(rhs)); 64 | } 65 | return (*this); 66 | } 67 | 68 | //------------------------------------------------------------------------------------------ 69 | // Not copyable 70 | mutex(const self_type &) = delete; 71 | self_type& operator =(const self_type &) = delete; 72 | 73 | public: 74 | //------------------------------------------------------------------------------------------ 75 | // User interface 76 | native_handle_type getNativeHandle() const { return base_type::getNativeHandle(); } 77 | bool isValid() const { return base_type::isValid(); } 78 | bool tryLock() { return base_type::tryLock(); } 79 | 80 | //------------------------------------------------------------------------------------------ 81 | // STL BasicLockable requirements: https://en.cppreference.com/w/cpp/named_req/BasicLockable 82 | bool lock() { return base_type::lock(); } 83 | void unlock() { return base_type::unlock(); } 84 | 85 | //------------------------------------------------------------------------------------------ 86 | // STL Lockable requirements: https://en.cppreference.com/w/cpp/named_req/Lockable 87 | bool try_lock() { return tryLock(); } 88 | 89 | //------------------------------------------------------------------------------------------ 90 | // STL TimedLockable requirements: https://en.cppreference.com/w/cpp/named_req/TimedLockable 91 | template 92 | bool try_lock_for(const std::chrono::duration<_Rep, _Period>& relTime) 93 | { 94 | return tryLockFor(relTime); 95 | } 96 | //------------------------------------------------------------------------------------------ 97 | template 98 | bool try_lock_until(const std::chrono::time_point<_Clock, _Duration>& absTime) 99 | { 100 | return tryLockUntil(absTime); 101 | } 102 | 103 | //------------------------------------------------------------------------------------------ 104 | template 105 | inline bool tryLockFor(const std::chrono::duration<_Rep, _Period>& relTime) 106 | { 107 | return base_type::tryLockFor(relTime); 108 | } 109 | //------------------------------------------------------------------------------------------ 110 | template 111 | inline bool tryLockUntil(const std::chrono::time_point<_Clock, _Duration>& absTime) 112 | { 113 | return tryLockFor(absTime - _Clock::now()); 114 | } 115 | }; 116 | 117 | } /*itfc*/ } /*oqpi*/ 118 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/interface/interface_semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/empty_layer.hpp" 6 | #include "oqpi/synchronization/sync_common.hpp" 7 | 8 | 9 | namespace oqpi { namespace itfc { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | template 13 | < 14 | // Platform specific implementation for semaphores 15 | typename _Impl 16 | // Augmentation layer, needs to be templated and inherit from the implementation 17 | , template typename _Layer 18 | > 19 | class semaphore 20 | : public std::conditional::value, _Impl, _Layer<_Impl>>::type 21 | { 22 | public: 23 | //------------------------------------------------------------------------------------------ 24 | // Whether the event has augmented layer(s) or not 25 | static constexpr auto is_lean = is_empty_layer<_Layer>::value; 26 | // The platform specific implementation 27 | using semaphore_impl = _Impl; 28 | // The actual base type taking into account the presence or absence of augmentation layer(s) 29 | using base_type = typename std::conditional_t>; 30 | // The actual type 31 | using self_type = semaphore; 32 | // Native handle 33 | using native_handle_type = typename semaphore_impl::native_handle_type; 34 | 35 | public: 36 | //------------------------------------------------------------------------------------------ 37 | semaphore(int32_t initCount = 0, int32_t maxCount = 0x7fffffff) 38 | : base_type(initCount, maxCount) 39 | {} 40 | 41 | //------------------------------------------------------------------------------------------ 42 | explicit semaphore(const std::string &name, int32_t initCount = 0, int32_t maxCount = 0x7fffffff) 43 | : base_type(name, sync_object_creation_options::open_or_create, initCount, maxCount) 44 | {} 45 | 46 | //------------------------------------------------------------------------------------------ 47 | semaphore(const std::string &name, sync_object_creation_options creationOption, int32_t initCount = 0, int32_t maxCount = 0x7fffffff) 48 | : base_type(name, creationOption, initCount, maxCount) 49 | {} 50 | 51 | public: 52 | //------------------------------------------------------------------------------------------ 53 | // Movable 54 | semaphore(self_type &&rhs) 55 | : base_type(std::move(*(base_type *)&rhs)) 56 | {} 57 | 58 | //------------------------------------------------------------------------------------------ 59 | inline self_type& operator =(self_type &&rhs) 60 | { 61 | if (this != &rhs) 62 | { 63 | base_type::operator =(std::move(rhs)); 64 | } 65 | return (*this); 66 | } 67 | 68 | //------------------------------------------------------------------------------------------ 69 | // Not copyable 70 | semaphore(const self_type &) = delete; 71 | self_type& operator =(const self_type &) = delete; 72 | 73 | public: 74 | //------------------------------------------------------------------------------------------ 75 | // User interface 76 | native_handle_type getNativeHandle() const { return base_type::getNativeHandle(); } 77 | bool isValid() const { return base_type::isValid(); } 78 | void notify(int32_t count) { return base_type::notify(count); } 79 | void notifyOne() { return notify(1); } 80 | void notifyAll() { return base_type::notifyAll(); } 81 | bool tryWait() { return base_type::tryWait(); } 82 | bool wait() { return base_type::wait(); } 83 | 84 | //------------------------------------------------------------------------------------------ 85 | template 86 | inline bool waitFor(const std::chrono::duration<_Rep, _Period>& relTime) 87 | { 88 | return base_type::waitFor(relTime); 89 | } 90 | //------------------------------------------------------------------------------------------ 91 | template 92 | inline bool waitUntil(const std::chrono::time_point<_Clock, _Duration>& absTime) 93 | { 94 | return waitFor(absTime - _Clock::now()); 95 | } 96 | }; 97 | 98 | } /*itfc*/ } /*oqpi*/ 99 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/mutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | 5 | // Interface 6 | #include "oqpi/synchronization/interface/interface_mutex.hpp" 7 | // Platform specific implementations 8 | #if OQPI_PLATFORM_WIN 9 | # include "oqpi/synchronization/win/win_mutex.hpp" 10 | #elif OQPI_PLATFORM_POSIX 11 | # include "oqpi/synchronization/posix/posix_mutex.hpp" 12 | #else 13 | # error No mutex implementation defined for the current platform 14 | #endif 15 | 16 | namespace oqpi { 17 | 18 | //---------------------------------------------------------------------------------------------- 19 | template typename _Layer = local_sync_object> 20 | using mutex_interface = itfc::mutex; 21 | //---------------------------------------------------------------------------------------------- 22 | 23 | #ifdef OQPI_USE_DEFAULT 24 | //---------------------------------------------------------------------------------------------- 25 | using global_mutex = mutex_interface; 26 | #endif 27 | 28 | } /*oqpi*/ 29 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/posix/posix_event.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/platform.hpp" 6 | #include "oqpi/error_handling.hpp" 7 | #include "oqpi/synchronization/sync_common.hpp" 8 | #include "oqpi/synchronization/posix/posix_semaphore_wrapper.hpp" 9 | 10 | 11 | namespace oqpi { 12 | 13 | //---------------------------------------------------------------------------------------------- 14 | // Forward declaration of this platform semaphore implementation 15 | using event_manual_reset_policy_impl = struct posix_event_manual_reset_policy; 16 | //---------------------------------------------------------------------------------------------- 17 | template class posix_event; 18 | //---------------------------------------------------------------------------------------------- 19 | template 20 | using event_impl = class posix_event<_ResetPolicy>; 21 | //---------------------------------------------------------------------------------------------- 22 | 23 | 24 | //---------------------------------------------------------------------------------------------- 25 | template 26 | class posix_event 27 | : protected _ResetPolicy 28 | { 29 | protected: 30 | //------------------------------------------------------------------------------------------ 31 | using native_handle_type = sem_t*; 32 | 33 | protected: 34 | //------------------------------------------------------------------------------------------ 35 | posix_event(const std::string &name, sync_object_creation_options creationOption) 36 | : sem_(name, creationOption, 0u) 37 | { 38 | } 39 | 40 | //------------------------------------------------------------------------------------------ 41 | posix_event(posix_event &&rhs) 42 | : sem_(std::move(rhs.sem_)) 43 | { 44 | } 45 | 46 | //------------------------------------------------------------------------------------------ 47 | posix_event& operator =(posix_event &&rhs) 48 | { 49 | if (this != &rhs) 50 | { 51 | sem_ = std::move(rhs.sem_); 52 | } 53 | return (*this); 54 | } 55 | 56 | protected: 57 | //------------------------------------------------------------------------------------------ 58 | // User interface 59 | native_handle_type getNativeHandle() const 60 | { 61 | return sem_.getHandle(); 62 | } 63 | 64 | //------------------------------------------------------------------------------------------ 65 | bool isValid() const 66 | { 67 | return sem_.isValid(); 68 | } 69 | 70 | //------------------------------------------------------------------------------------------ 71 | void notify() 72 | { 73 | sem_.post(); 74 | } 75 | 76 | //------------------------------------------------------------------------------------------ 77 | bool wait() 78 | { 79 | if (!sem_.wait()) 80 | { 81 | return false; 82 | } 83 | 84 | // Only if its a manual reset event, then we also want to signal other potential waiters. 85 | // To signal other potential waiters, increment lock. Snowball effect. 86 | if (_ResetPolicy::is_manual_reset_enabled()) 87 | { 88 | sem_.post(); 89 | } 90 | 91 | return true; 92 | } 93 | 94 | //------------------------------------------------------------------------------------------ 95 | void reset() 96 | { 97 | _ResetPolicy::reset(sem_); 98 | } 99 | 100 | //------------------------------------------------------------------------------------------ 101 | template 102 | bool waitFor(const std::chrono::duration<_Rep, _Period>& relTime) 103 | { 104 | if (!sem_.waitFor(relTime)) 105 | { 106 | return false; 107 | } 108 | 109 | // Only if its a manual reset event, then we also want to signal other potential waiters. 110 | // increment lock. Snowball effect. 111 | if (_ResetPolicy::is_manual_reset_enabled()) 112 | { 113 | sem_.post(); 114 | } 115 | 116 | return true; 117 | } 118 | 119 | private: 120 | //------------------------------------------------------------------------------------------ 121 | // Not copyable 122 | posix_event(const posix_event &) = delete; 123 | posix_event& operator =(const posix_event &) = delete; 124 | 125 | private: 126 | //------------------------------------------------------------------------------------------ 127 | posix_semaphore_wrapper sem_; 128 | }; 129 | 130 | 131 | //---------------------------------------------------------------------------------------------- 132 | struct posix_event_manual_reset_policy 133 | { 134 | //------------------------------------------------------------------------------------------ 135 | static bool is_manual_reset_enabled() 136 | { 137 | return true; 138 | } 139 | 140 | //------------------------------------------------------------------------------------------ 141 | void reset(posix_semaphore_wrapper &sem) 142 | { 143 | auto semValue = sem.getValue(); 144 | 145 | // Decrement internal counter to 0. Event is unsignaled. 146 | while (semValue != 0) 147 | { 148 | if (!sem.wait()) 149 | { 150 | oqpi_error("Was not able to reset event."); 151 | break; 152 | } 153 | 154 | semValue = sem.getValue(); 155 | } 156 | } 157 | }; 158 | 159 | } /*oqpi*/ 160 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/posix/posix_mutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | #include "oqpi/error_handling.hpp" 5 | #include "oqpi/synchronization/sync_common.hpp" 6 | #include "oqpi/synchronization/posix/posix_semaphore_wrapper.hpp" 7 | 8 | 9 | namespace oqpi { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | // Forward declaration of this platform mutex implementation 13 | using mutex_impl = class posix_mutex; 14 | 15 | //---------------------------------------------------------------------------------------------- 16 | class posix_mutex 17 | { 18 | protected: 19 | //------------------------------------------------------------------------------------------ 20 | using native_handle_type = sem_t*; 21 | 22 | protected: 23 | //------------------------------------------------------------------------------------------ 24 | posix_mutex(const std::string &name, sync_object_creation_options creationOption, bool lockOnCreation) 25 | : sem_() 26 | { 27 | // A mutex is simply a binary semaphore! 28 | const auto initCount = lockOnCreation ? 0u : 1u; 29 | sem_ = posix_semaphore_wrapper(name, creationOption, initCount); 30 | } 31 | 32 | //------------------------------------------------------------------------------------------ 33 | posix_mutex(posix_mutex &&other) 34 | : sem_(std::move(other.sem_)) 35 | { 36 | } 37 | 38 | //------------------------------------------------------------------------------------------ 39 | posix_mutex &operator=(posix_mutex &&rhs) 40 | { 41 | if (this != &rhs) 42 | { 43 | sem_ = std::move(rhs.sem_); 44 | } 45 | return (*this); 46 | } 47 | 48 | protected: 49 | //------------------------------------------------------------------------------------------ 50 | // User interface 51 | native_handle_type getNativeHandle() const 52 | { 53 | return sem_.getHandle(); 54 | } 55 | 56 | //------------------------------------------------------------------------------------------ 57 | bool isValid() const 58 | { 59 | return sem_.isValid(); 60 | } 61 | 62 | //------------------------------------------------------------------------------------------ 63 | bool lock() 64 | { 65 | return sem_.wait(); 66 | } 67 | 68 | //------------------------------------------------------------------------------------------ 69 | bool tryLock() 70 | { 71 | return sem_.tryWait(); 72 | } 73 | 74 | //------------------------------------------------------------------------------------------ 75 | template 76 | bool tryLockFor(const std::chrono::duration<_Rep, _Period> &relTime) 77 | { 78 | return sem_.waitFor(relTime); 79 | } 80 | 81 | //------------------------------------------------------------------------------------------ 82 | void unlock() 83 | { 84 | const auto semValue = sem_.getValue(); 85 | if (semValue >= 1) 86 | { 87 | oqpi_error("You cannot unlock a mutex more than once."); 88 | return; 89 | } 90 | 91 | sem_.post(); 92 | } 93 | 94 | private: 95 | //------------------------------------------------------------------------------------------ 96 | // Not copyable 97 | posix_mutex(const posix_mutex &) = delete; 98 | 99 | posix_mutex &operator=(const posix_mutex &) = delete; 100 | 101 | private: 102 | //------------------------------------------------------------------------------------------ 103 | posix_semaphore_wrapper sem_; 104 | }; 105 | } /*oqpi*/ 106 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/posix/posix_semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | #include "oqpi/error_handling.hpp" 5 | #include "oqpi/synchronization/sync_common.hpp" 6 | #include "oqpi/synchronization/posix/posix_semaphore_wrapper.hpp" 7 | 8 | 9 | namespace oqpi { 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | // Forward declaration of this platform semaphore implementation 13 | using semaphore_impl = class posix_semaphore; 14 | 15 | 16 | //---------------------------------------------------------------------------------------------- 17 | class posix_semaphore 18 | { 19 | protected: 20 | //------------------------------------------------------------------------------------------ 21 | using native_handle_type = sem_t*; 22 | 23 | protected: 24 | //------------------------------------------------------------------------------------------ 25 | posix_semaphore(const std::string &name, sync_object_creation_options creationOption, int32_t initCount, int32_t maxCount) 26 | : maxCount_(maxCount) 27 | , sem_(name, creationOption, initCount) 28 | { 29 | } 30 | 31 | //------------------------------------------------------------------------------------------ 32 | posix_semaphore(posix_semaphore &&other) 33 | : sem_(std::move(other.sem_)) 34 | , maxCount_(other.maxCount_) 35 | { 36 | } 37 | 38 | //------------------------------------------------------------------------------------------ 39 | posix_semaphore& operator =(posix_semaphore &&rhs) 40 | { 41 | if (this != &rhs) 42 | { 43 | sem_ = std::move(rhs.sem_); 44 | maxCount_ = rhs.maxCount_; 45 | } 46 | return (*this); 47 | } 48 | 49 | protected: 50 | //------------------------------------------------------------------------------------------ 51 | // User interface 52 | native_handle_type getNativeHandle() const 53 | { 54 | return sem_.getHandle(); 55 | } 56 | 57 | //------------------------------------------------------------------------------------------ 58 | bool isValid() const 59 | { 60 | return sem_.isValid(); 61 | } 62 | 63 | //------------------------------------------------------------------------------------------ 64 | void notify(int32_t count) 65 | { 66 | for(auto i = 0; i < count; ++i) 67 | { 68 | sem_.post(); 69 | } 70 | } 71 | 72 | //------------------------------------------------------------------------------------------ 73 | void notifyAll() 74 | { 75 | notify(maxCount_); 76 | } 77 | 78 | //------------------------------------------------------------------------------------------ 79 | bool tryWait() 80 | { 81 | return sem_.tryWait(); 82 | } 83 | 84 | //------------------------------------------------------------------------------------------ 85 | bool wait() 86 | { 87 | return sem_.wait(); 88 | } 89 | 90 | //------------------------------------------------------------------------------------------ 91 | template 92 | bool waitFor(const std::chrono::duration<_Rep, _Period> &relTime) 93 | { 94 | sem_.waitFor(relTime); 95 | } 96 | 97 | private: 98 | //------------------------------------------------------------------------------------------ 99 | // Not copyable 100 | posix_semaphore(const posix_semaphore &) = delete; 101 | 102 | posix_semaphore& operator =(const posix_semaphore &) = delete; 103 | 104 | private: 105 | //------------------------------------------------------------------------------------------ 106 | int32_t maxCount_; 107 | posix_semaphore_wrapper sem_; 108 | }; 109 | 110 | } /*oqpi*/ 111 | 112 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/posix/posix_sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | 5 | 6 | namespace oqpi { 7 | 8 | //---------------------------------------------------------------------------------------------- 9 | using sync_impl = class posix_sync; 10 | 11 | //---------------------------------------------------------------------------------------------- 12 | class posix_sync 13 | { 14 | public: 15 | //------------------------------------------------------------------------------------------ 16 | template 17 | static auto wait_indefinitely_for_any(_SyncObjects &&...syncObjects) 18 | { 19 | static_assert(_False, "Not implemented yet."); 20 | return 0; 21 | } 22 | }; 23 | 24 | } /*oqpi*/ 25 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | 5 | // Interface 6 | #include "oqpi/synchronization/interface/interface_semaphore.hpp" 7 | // Platform specific implementations 8 | #if OQPI_PLATFORM_WIN 9 | # include "oqpi/synchronization/win/win_semaphore.hpp" 10 | #elif OQPI_PLATFORM_POSIX 11 | # include "oqpi/synchronization/posix/posix_semaphore.hpp" 12 | #else 13 | # error No semaphore implementation defined for the current platform 14 | #endif 15 | 16 | namespace oqpi { 17 | 18 | //---------------------------------------------------------------------------------------------- 19 | template typename _Layer = local_sync_object> 20 | using semaphore_interface = itfc::semaphore; 21 | //---------------------------------------------------------------------------------------------- 22 | 23 | #ifdef OQPI_USE_DEFAULT 24 | //---------------------------------------------------------------------------------------------- 25 | using semaphore = semaphore_interface<>; 26 | using global_semaphore = semaphore_interface; 27 | //---------------------------------------------------------------------------------------------- 28 | #endif 29 | 30 | } /*oqpi*/ 31 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | 5 | //-------------------------------------------------------------------------------------------------- 6 | // Platform specific implementations of synchronization functions 7 | #if OQPI_PLATFORM_WIN 8 | # include "oqpi/synchronization/win/win_sync.hpp" 9 | #elif OQPI_PLATFORM_POSIX 10 | # include "oqpi/synchronization/posix/posix_sync.hpp" 11 | #else 12 | # error No sync functions implementation defined for the current platform 13 | #endif 14 | 15 | namespace oqpi { 16 | 17 | //---------------------------------------------------------------------------------------------- 18 | using sync = sync_impl; 19 | 20 | } /*oqpi*/ 21 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/sync_common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace oqpi { 7 | 8 | //---------------------------------------------------------------------------------------------- 9 | enum class sync_object_creation_options 10 | { 11 | create_if_nonexistent, 12 | open_existing, 13 | open_or_create, 14 | }; 15 | 16 | 17 | //---------------------------------------------------------------------------------------------- 18 | template 19 | class local_sync_object 20 | : protected _Impl 21 | { 22 | protected: 23 | //------------------------------------------------------------------------------------------ 24 | template 25 | local_sync_object(_Args &&...args) 26 | : _Impl("", sync_object_creation_options::open_or_create, std::forward<_Args>(args)...) 27 | {} 28 | 29 | //------------------------------------------------------------------------------------------ 30 | template 31 | local_sync_object(const std::string &, sync_object_creation_options, _Args &&...args) 32 | : local_sync_object(std::forward<_Args>(args)...) 33 | { 34 | fail(); 35 | } 36 | 37 | //------------------------------------------------------------------------------------------ 38 | // Clang will evaluate a static_assert(false) even if the function is never called 39 | // So we must trick it to not evaluate it by templating the assert condition 40 | template 41 | void fail() 42 | { 43 | static_assert(_UndefinedFunction, "A local synchronization object cannot have a name or creation option."); 44 | } 45 | }; 46 | 47 | 48 | //---------------------------------------------------------------------------------------------- 49 | template 50 | class global_sync_object 51 | : protected _Impl 52 | { 53 | protected: 54 | //------------------------------------------------------------------------------------------ 55 | global_sync_object() 56 | { 57 | fail(); 58 | } 59 | 60 | //------------------------------------------------------------------------------------------ 61 | template 62 | global_sync_object(const std::string &name, sync_object_creation_options creationOption, _Args &&...args) 63 | : _Impl(name, creationOption, std::forward<_Args>(args)...) 64 | , name_(name) 65 | {} 66 | 67 | public: 68 | //------------------------------------------------------------------------------------------ 69 | const auto& getName() const { return name_; } 70 | 71 | private: 72 | //------------------------------------------------------------------------------------------ 73 | // Clang will evaluate a static_assert(false) even if the function is never called 74 | // So we must trick it to not evaluate it by templating the assert condition 75 | template 76 | void fail() 77 | { 78 | static_assert(_UndefinedFunction, "You must name the global synchronization object."); 79 | } 80 | 81 | private: 82 | //------------------------------------------------------------------------------------------ 83 | std::string name_; 84 | }; 85 | 86 | } /*oqpi*/ 87 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/win/win_event.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | #include "oqpi/error_handling.hpp" 5 | #include "oqpi/synchronization/sync_common.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | // Forward declaration of this platform semaphore implementation 12 | using event_manual_reset_policy_impl = struct win_event_manual_reset_policy; 13 | //---------------------------------------------------------------------------------------------- 14 | template class win_event; 15 | //---------------------------------------------------------------------------------------------- 16 | template 17 | using event_impl = class win_event<_ResetPolicy>; 18 | //---------------------------------------------------------------------------------------------- 19 | 20 | 21 | //---------------------------------------------------------------------------------------------- 22 | template 23 | class win_event 24 | : protected _ResetPolicy 25 | { 26 | protected: 27 | //------------------------------------------------------------------------------------------ 28 | using native_handle_type = HANDLE; 29 | 30 | protected: 31 | //------------------------------------------------------------------------------------------ 32 | win_event(const std::string &name, sync_object_creation_options creationOption) 33 | : handle_(nullptr) 34 | { 35 | const auto bManualReset = BOOL{ _ResetPolicy::is_manual_reset_enabled() }; 36 | const auto bInitialState = BOOL{ FALSE }; 37 | 38 | if (creationOption == sync_object_creation_options::open_existing) 39 | { 40 | oqpi_check(!name.empty()); 41 | handle_ = OpenEventA(EVENT_ALL_ACCESS, FALSE, name.c_str()); 42 | } 43 | else 44 | { 45 | handle_ = CreateEventA(nullptr, bManualReset, bInitialState, name.empty() ? nullptr : name.c_str()); 46 | if (creationOption == sync_object_creation_options::create_if_nonexistent && GetLastError() == ERROR_ALREADY_EXISTS) 47 | { 48 | close(); 49 | handle_ = nullptr; 50 | } 51 | } 52 | } 53 | 54 | //------------------------------------------------------------------------------------------ 55 | win_event(win_event &&rhs) 56 | : handle_(rhs.handle_) 57 | { 58 | rhs.handle_ = nullptr; 59 | } 60 | 61 | //------------------------------------------------------------------------------------------ 62 | ~win_event() 63 | { 64 | close(); 65 | } 66 | 67 | //------------------------------------------------------------------------------------------ 68 | win_event& operator =(win_event &&rhs) 69 | { 70 | if (this != &rhs) 71 | { 72 | handle_ = rhs.handle_; 73 | rhs.handle_ = nullptr; 74 | } 75 | return (*this); 76 | } 77 | 78 | protected: 79 | //------------------------------------------------------------------------------------------ 80 | // User interface 81 | native_handle_type getNativeHandle() const 82 | { 83 | return handle_; 84 | } 85 | 86 | //------------------------------------------------------------------------------------------ 87 | bool isValid() const 88 | { 89 | return handle_ != nullptr; 90 | } 91 | 92 | //------------------------------------------------------------------------------------------ 93 | void notify() 94 | { 95 | oqpi_verify(SetEvent(handle_) == TRUE); 96 | } 97 | 98 | //------------------------------------------------------------------------------------------ 99 | bool wait() 100 | { 101 | return internalWait(INFINITE, TRUE); 102 | } 103 | 104 | //------------------------------------------------------------------------------------------ 105 | void reset() 106 | { 107 | _ResetPolicy::reset(handle_); 108 | } 109 | 110 | //------------------------------------------------------------------------------------------ 111 | template 112 | bool waitFor(const std::chrono::duration<_Rep, _Period>& relTime) 113 | { 114 | const auto dwMilliseconds = DWORD(std::chrono::duration_cast(relTime).count()); 115 | return internalWait(dwMilliseconds, TRUE); 116 | } 117 | 118 | //------------------------------------------------------------------------------------------ 119 | void close() 120 | { 121 | if (handle_) 122 | { 123 | CloseHandle(handle_); 124 | handle_ = nullptr; 125 | } 126 | } 127 | 128 | private: 129 | //------------------------------------------------------------------------------------------ 130 | bool internalWait(DWORD dwMilliseconds, BOOL bAlertable) 131 | { 132 | const auto result = WaitForSingleObjectEx(handle_, dwMilliseconds, bAlertable); 133 | if (oqpi_failed(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT)) 134 | { 135 | oqpi_error("WaitForSingleObjectEx failed with error code 0x%x", GetLastError()); 136 | } 137 | return (result == WAIT_OBJECT_0); 138 | } 139 | 140 | private: 141 | //------------------------------------------------------------------------------------------ 142 | // Not copyable 143 | win_event(const win_event &) = delete; 144 | win_event& operator =(const win_event &) = delete; 145 | 146 | private: 147 | //------------------------------------------------------------------------------------------ 148 | native_handle_type handle_; 149 | }; 150 | 151 | 152 | //---------------------------------------------------------------------------------------------- 153 | struct win_event_manual_reset_policy 154 | { 155 | static bool is_manual_reset_enabled() 156 | { 157 | return true; 158 | } 159 | 160 | void reset(HANDLE handle) 161 | { 162 | oqpi_verify(ResetEvent(handle) != FALSE); 163 | } 164 | }; 165 | 166 | } /*oqpi*/ 167 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/win/win_mutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | #include "oqpi/error_handling.hpp" 5 | #include "oqpi/synchronization/sync_common.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | // Forward declaration of this platform mutex implementation 12 | using mutex_impl = class win_mutex; 13 | 14 | 15 | //---------------------------------------------------------------------------------------------- 16 | class win_mutex 17 | { 18 | protected: 19 | //------------------------------------------------------------------------------------------ 20 | using native_handle_type = HANDLE; 21 | 22 | protected: 23 | //------------------------------------------------------------------------------------------ 24 | win_mutex(const std::string &name, sync_object_creation_options creationOption, bool lockOnCreation) 25 | : handle_(nullptr) 26 | { 27 | if (creationOption == sync_object_creation_options::open_existing) 28 | { 29 | oqpi_check(!name.empty()); 30 | handle_ = OpenMutexA(MUTEX_ALL_ACCESS, FALSE, name.c_str()); 31 | } 32 | else 33 | { 34 | handle_ = CreateMutexA(nullptr, lockOnCreation, name.empty() ? nullptr : name.c_str()); 35 | if ((handle_ != nullptr) && (creationOption == sync_object_creation_options::create_if_nonexistent) && (GetLastError() == ERROR_ALREADY_EXISTS)) 36 | { 37 | CloseHandle(handle_); 38 | handle_ = nullptr; 39 | } 40 | } 41 | } 42 | 43 | //------------------------------------------------------------------------------------------ 44 | ~win_mutex() 45 | { 46 | if (handle_) 47 | { 48 | CloseHandle(handle_); 49 | handle_ = nullptr; 50 | } 51 | } 52 | 53 | //------------------------------------------------------------------------------------------ 54 | win_mutex(win_mutex &&other) 55 | : handle_(other.handle_) 56 | { 57 | other.handle_ = nullptr; 58 | } 59 | 60 | //------------------------------------------------------------------------------------------ 61 | win_mutex& operator =(win_mutex &&rhs) 62 | { 63 | if (this != &rhs) 64 | { 65 | handle_ = rhs.handle_; 66 | rhs.handle_ = nullptr; 67 | } 68 | return (*this); 69 | } 70 | 71 | protected: 72 | //------------------------------------------------------------------------------------------ 73 | // User interface 74 | native_handle_type getNativeHandle() const 75 | { 76 | return handle_; 77 | } 78 | 79 | //------------------------------------------------------------------------------------------ 80 | bool isValid() const 81 | { 82 | return handle_ != nullptr; 83 | } 84 | 85 | //------------------------------------------------------------------------------------------ 86 | bool lock() 87 | { 88 | return internalWait(INFINITE, TRUE); 89 | } 90 | 91 | //------------------------------------------------------------------------------------------ 92 | bool tryLock() 93 | { 94 | return internalWait(0, TRUE); 95 | } 96 | 97 | //------------------------------------------------------------------------------------------ 98 | template 99 | bool tryLockFor(const std::chrono::duration<_Rep, _Period>& relTime) 100 | { 101 | const auto dwMilliseconds = DWORD(std::chrono::duration_cast(relTime).count()); 102 | return internalWait(dwMilliseconds, TRUE); 103 | } 104 | 105 | //------------------------------------------------------------------------------------------ 106 | void unlock() 107 | { 108 | oqpi_verify(ReleaseMutex(handle_) != FALSE); 109 | } 110 | 111 | private: 112 | //------------------------------------------------------------------------------------------ 113 | bool internalWait(DWORD dwMilliseconds, BOOL bAlertable) 114 | { 115 | const auto result = WaitForSingleObjectEx(handle_, dwMilliseconds, bAlertable); 116 | if (result == WAIT_FAILED) 117 | { 118 | oqpi_error("WaitForSingleObjectEx failed with error code 0x%x", GetLastError()); 119 | } 120 | return (result == WAIT_OBJECT_0); 121 | } 122 | 123 | private: 124 | //------------------------------------------------------------------------------------------ 125 | // Not copyable 126 | win_mutex(const win_mutex &) = delete; 127 | win_mutex& operator =(const win_mutex &) = delete; 128 | 129 | private: 130 | //------------------------------------------------------------------------------------------ 131 | HANDLE handle_; 132 | }; 133 | 134 | } /*oqpi*/ 135 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/win/win_semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | #include "oqpi/error_handling.hpp" 5 | #include "oqpi/synchronization/sync_common.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | // Forward declaration of this platform semaphore implementation 12 | using semaphore_impl = class win_semaphore; 13 | 14 | 15 | //---------------------------------------------------------------------------------------------- 16 | class win_semaphore 17 | { 18 | protected: 19 | //------------------------------------------------------------------------------------------ 20 | using native_handle_type = HANDLE; 21 | 22 | protected: 23 | //------------------------------------------------------------------------------------------ 24 | win_semaphore(const std::string &name, sync_object_creation_options creationOption, int32_t initCount, int32_t maxCount) 25 | : initCount_(initCount) 26 | , maxCount_(maxCount) 27 | , handle_(nullptr) 28 | { 29 | if (creationOption == sync_object_creation_options::open_existing) 30 | { 31 | oqpi_check(!name.empty()); 32 | handle_ = OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, FALSE, name.c_str()); 33 | } 34 | else 35 | { 36 | handle_ = CreateSemaphoreA(nullptr, initCount_, maxCount_, name.empty() ? nullptr : name.c_str()); 37 | if (creationOption == sync_object_creation_options::create_if_nonexistent && GetLastError() == ERROR_ALREADY_EXISTS && handle_) 38 | { 39 | CloseHandle(handle_); 40 | handle_ = nullptr; 41 | } 42 | } 43 | } 44 | 45 | //------------------------------------------------------------------------------------------ 46 | ~win_semaphore() 47 | { 48 | if (handle_) 49 | { 50 | CloseHandle(handle_); 51 | handle_ = nullptr; 52 | } 53 | } 54 | 55 | //------------------------------------------------------------------------------------------ 56 | win_semaphore(win_semaphore &&other) noexcept 57 | : handle_(other.handle_) 58 | , initCount_(other.initCount_) 59 | , maxCount_(other.maxCount_) 60 | { 61 | other.handle_ = nullptr; 62 | } 63 | 64 | //------------------------------------------------------------------------------------------ 65 | win_semaphore& operator =(win_semaphore &&rhs) noexcept 66 | { 67 | if (this != &rhs) 68 | { 69 | handle_ = rhs.handle_; 70 | initCount_ = rhs.initCount_; 71 | maxCount_ = rhs.maxCount_; 72 | rhs.handle_ = nullptr; 73 | } 74 | return (*this); 75 | } 76 | 77 | protected: 78 | //------------------------------------------------------------------------------------------ 79 | // User interface 80 | native_handle_type getNativeHandle() const 81 | { 82 | return handle_; 83 | } 84 | 85 | //------------------------------------------------------------------------------------------ 86 | bool isValid() const 87 | { 88 | return handle_ != nullptr; 89 | } 90 | 91 | //------------------------------------------------------------------------------------------ 92 | void notify(int32_t count) 93 | { 94 | auto previousCount = LONG{ 0 }; 95 | oqpi_verify(ReleaseSemaphore(handle_, LONG{ count }, &previousCount) != 0); 96 | } 97 | 98 | //------------------------------------------------------------------------------------------ 99 | void notifyAll() 100 | { 101 | notify(maxCount_); 102 | } 103 | 104 | //------------------------------------------------------------------------------------------ 105 | bool tryWait() 106 | { 107 | return internalWait(0, TRUE); 108 | } 109 | 110 | //------------------------------------------------------------------------------------------ 111 | bool wait() 112 | { 113 | return internalWait(INFINITE, TRUE); 114 | } 115 | 116 | //------------------------------------------------------------------------------------------ 117 | template 118 | bool waitFor(const std::chrono::duration<_Rep, _Period>& relTime) 119 | { 120 | const auto dwMilliseconds = DWORD(std::chrono::duration_cast(relTime).count()); 121 | return internalWait(dwMilliseconds, TRUE); 122 | } 123 | 124 | private: 125 | //------------------------------------------------------------------------------------------ 126 | bool internalWait(DWORD dwMilliseconds, BOOL bAlertable) 127 | { 128 | const auto result = WaitForSingleObjectEx(handle_, dwMilliseconds, bAlertable); 129 | if (oqpi_failed(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT)) 130 | { 131 | oqpi_error("WaitForSingleObjectEx failed with error code 0x%x", GetLastError()); 132 | } 133 | return (result == WAIT_OBJECT_0); 134 | } 135 | 136 | private: 137 | //------------------------------------------------------------------------------------------ 138 | // Not copyable 139 | win_semaphore(const win_semaphore &) = delete; 140 | win_semaphore& operator =(const win_semaphore &) = delete; 141 | 142 | private: 143 | //------------------------------------------------------------------------------------------ 144 | LONG initCount_; 145 | LONG maxCount_; 146 | HANDLE handle_; 147 | }; 148 | 149 | } /*oqpi*/ 150 | -------------------------------------------------------------------------------- /include/oqpi/synchronization/win/win_sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "oqpi/platform.hpp" 6 | 7 | 8 | namespace oqpi { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | using sync_impl = class win_sync; 12 | 13 | //---------------------------------------------------------------------------------------------- 14 | class win_sync 15 | { 16 | public: 17 | //------------------------------------------------------------------------------------------ 18 | template 19 | static auto wait_indefinitely_for_any(_SyncObjects &&...syncObjects) 20 | { 21 | auto handles = make_handle_array(std::forward<_SyncObjects>(syncObjects)...); 22 | return WaitForMultipleObjects(DWORD(handles.size()), handles.data(), FALSE, INFINITE); 23 | } 24 | 25 | private: 26 | //------------------------------------------------------------------------------------------ 27 | template 28 | static void make_handle(const _SyncObject &syncObject, HANDLE &handle) 29 | { 30 | handle = syncObject.getNativeHandle(); 31 | } 32 | 33 | //------------------------------------------------------------------------------------------ 34 | template 35 | static auto make_handle_array(_SyncObjects &&...syncObjects) 36 | { 37 | auto handles = std::array{}; 38 | auto i = 0; 39 | (make_handle(syncObjects, handles[i++]), ...); 40 | return handles; 41 | } 42 | }; 43 | 44 | } /*oqpi*/ 45 | -------------------------------------------------------------------------------- /include/oqpi/threading.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/threading/thread.hpp" 4 | #include "oqpi/threading/this_thread.hpp" 5 | -------------------------------------------------------------------------------- /include/oqpi/threading/interface_thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "oqpi/empty_layer.hpp" 9 | #include "oqpi/threading/thread_attributes.hpp" 10 | 11 | 12 | namespace oqpi { namespace itfc { 13 | 14 | //---------------------------------------------------------------------------------------------- 15 | // Thread interface, all thread implementation need to comply to this interface 16 | template 17 | < 18 | // Platform specific implementation for a thread 19 | typename _Impl 20 | // Augmentation layer, needs to be templated and inherit from the implementation 21 | , template typename _Layer = empty_layer 22 | > 23 | class thread 24 | : public std::conditional::value, _Impl, _Layer<_Impl>>::type 25 | { 26 | public: 27 | //------------------------------------------------------------------------------------------ 28 | // Whether the thread has augmented layer(s) or not 29 | static constexpr auto is_lean = is_empty_layer<_Layer>::value; 30 | // The platform specific implementation 31 | using thread_impl = _Impl; 32 | // The actual base type taking into account the presence or absence of augmentation layer(s) 33 | using base_type = typename std::conditional>::type; 34 | // The actual type 35 | using self_type = thread; 36 | 37 | public: 38 | //------------------------------------------------------------------------------------------ 39 | // Returns the number of hardware threads (logical cores) 40 | static unsigned int hardware_concurrency() { return thread_impl::hardware_concurrency(); } 41 | 42 | public: 43 | //------------------------------------------------------------------------------------------ 44 | // Default constructible, constructs a non-joinable thread 45 | thread() = default; 46 | 47 | //------------------------------------------------------------------------------------------ 48 | // Constructor from any callable object, creates a thread and runs the passed function 49 | // passing it the any arguments it needs. The arguments have to be provided. 50 | // See thread_attributes for more info on how to configure the thread. 51 | template 52 | explicit thread(const thread_attributes &attributes, _Func &&func, _Args &&...args) 53 | { 54 | launch(attributes, std::tuple, std::decay_t<_Args>...>(std::forward<_Func>(func), std::forward<_Args>(args)...)); 55 | } 56 | 57 | //------------------------------------------------------------------------------------------ 58 | // Creates a thread specifying only its name. Uses default thread configuration. 59 | template 60 | explicit thread(const std::string &name, _Func &&func, _Args &&...args) 61 | : thread(thread_attributes(name), std::forward<_Func>(func), std::forward<_Args>(args)...) 62 | {} 63 | 64 | //------------------------------------------------------------------------------------------ 65 | // Kills the thread if it's still joinable on destruction 66 | ~thread() noexcept 67 | { 68 | if (joinable()) 69 | { 70 | std::terminate(); 71 | } 72 | } 73 | 74 | public: 75 | //------------------------------------------------------------------------------------------ 76 | // Movable 77 | thread(self_type &&other) 78 | : base_type(std::move(other)) 79 | {} 80 | //------------------------------------------------------------------------------------------ 81 | self_type& operator =(self_type &&rhs) 82 | { 83 | if (this != &rhs) 84 | { 85 | if (joinable()) 86 | { 87 | std::terminate(); 88 | } 89 | thread_impl::operator =(std::move(rhs)); 90 | } 91 | return (*this); 92 | } 93 | 94 | private: 95 | //------------------------------------------------------------------------------------------ 96 | // Not copyable 97 | thread(const self_type &) = delete; 98 | self_type& operator =(const self_type &) = delete; 99 | 100 | 101 | public: 102 | //------------------------------------------------------------------------------------------ 103 | // Forward native type definitions 104 | using id = typename thread_impl::id; 105 | using native_handle_type = typename thread_impl::native_handle_type; 106 | 107 | public: 108 | //------------------------------------------------------------------------------------------ 109 | // Public interface that needs to be implemented by the thread implementation 110 | id getId() const { return thread_impl::getId(); } 111 | native_handle_type getNativeHandle() const { return thread_impl::getNativeHandle(); } 112 | 113 | bool joinable() const { return thread_impl::joinable(); } 114 | void join() { return thread_impl::join(); } 115 | void detach() { return thread_impl::detach(); } 116 | void terminate() { return thread_impl::terminate(); } 117 | 118 | void cancelSynchronousIO() { return thread_impl::cancelSynchronousIO(); } 119 | 120 | void setCoreAffinityMask(core_affinity affinity) { return thread_impl::setCoreAffinityMask(affinity); } 121 | core_affinity getCoreAffinityMask() const { return thread_impl::getCoreAffinityMask(); } 122 | 123 | void setPriority(thread_priority priority) { return thread_impl::setPriority(priority); } 124 | thread_priority getPriority() const { return thread_impl::getPriority(); } 125 | 126 | public: 127 | //------------------------------------------------------------------------------------------ 128 | // Helpers 129 | void setCoreAffinity(int32_t coreNumber) 130 | { 131 | setCoreAffinityMask(core_affinity(1 << coreNumber)); 132 | } 133 | 134 | private: 135 | //------------------------------------------------------------------------------------------ 136 | // Intermediary structure used to launch a thread. It is templated by the tuple holding 137 | // the function as well as the needed parameters to pass to the said function. 138 | template 139 | struct launcher 140 | { 141 | launcher(const thread_attributes &attributes, _Tuple &&t) 142 | : attributes_(attributes) 143 | , tuple_(std::move(t)) 144 | {} 145 | 146 | inline void operator()() 147 | { 148 | thread_impl::set_name(thread_impl::get_current_thread_native_handle(), attributes_.name_.c_str()); 149 | run(std::make_integer_sequence::value>()); 150 | } 151 | 152 | template 153 | void run(std::integer_sequence) 154 | { 155 | std::invoke(std::move(std::get<_Indices>(tuple_))...); 156 | } 157 | 158 | const thread_attributes attributes_; 159 | _Tuple tuple_; 160 | }; 161 | 162 | //------------------------------------------------------------------------------------------ 163 | // This function is responsible for the actual thread creation and launch. 164 | // The template tuple packs the function to call alongside its needed parameters. 165 | template 166 | void launch(const thread_attributes &attributes, _Tuple &&tuple) 167 | { 168 | // Actual type of the launcher 169 | using launcher_t = launcher<_Tuple>; 170 | // Create a launcher on the heap to be able to pass it to the thread. 171 | // The unique_ptr will make sure that the resource will be freed if anything goes wrong. 172 | auto upLauncher = std::make_unique(attributes, std::forward<_Tuple>(tuple)); 173 | 174 | // Actually creates the thread. Passed a pointer to the launcher as user data. 175 | // The implementation should take the ownership of the launcher if the thread creation succeeds. 176 | if (thread_impl::template create(attributes, upLauncher.get())) 177 | { 178 | // If the thread creation succeeded, we transfer the ownership of the launcher to it. 179 | upLauncher.release(); 180 | } 181 | } 182 | }; 183 | 184 | } /*itfc*/ } /*oqpi*/ -------------------------------------------------------------------------------- /include/oqpi/threading/this_thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "oqpi/threading/thread.hpp" 5 | #include "oqpi/threading/thread_attributes.hpp" 6 | 7 | 8 | namespace oqpi { namespace this_thread { 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | inline void set_name(const char *name); 12 | //---------------------------------------------------------------------------------------------- 13 | 14 | //---------------------------------------------------------------------------------------------- 15 | // Retrieves the number of the processor the current thread was running on during 16 | // the call to this function. 17 | uint32_t get_current_core(); 18 | //---------------------------------------------------------------------------------------------- 19 | 20 | //---------------------------------------------------------------------------------------------- 21 | void yield() noexcept; 22 | //---------------------------------------------------------------------------------------------- 23 | 24 | //---------------------------------------------------------------------------------------------- 25 | void set_priority(thread_priority threadPriority); 26 | //---------------------------------------------------------------------------------------------- 27 | 28 | //---------------------------------------------------------------------------------------------- 29 | void set_affinity_mask(core_affinity coreAffinityMask); 30 | //---------------------------------------------------------------------------------------------- 31 | inline void set_affinity(uint32_t coreNumber) 32 | { 33 | set_affinity_mask(core_affinity(1ul << coreNumber)); 34 | } 35 | //---------------------------------------------------------------------------------------------- 36 | 37 | //---------------------------------------------------------------------------------------------- 38 | auto get_id(); 39 | //---------------------------------------------------------------------------------------------- 40 | 41 | //---------------------------------------------------------------------------------------------- 42 | template 43 | void sleep_for(const std::chrono::duration<_Rep, _Period>& relTime); 44 | //---------------------------------------------------------------------------------------------- 45 | template 46 | inline void sleep_until(const std::chrono::time_point<_Clock, _Duration>& absTime) 47 | { 48 | sleep_for(absTime - _Clock::now()); 49 | } 50 | //---------------------------------------------------------------------------------------------- 51 | 52 | } /*this_thread*/ } /*oqpi*/ -------------------------------------------------------------------------------- /include/oqpi/threading/thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "oqpi/platform.hpp" 4 | 5 | // Thread interface 6 | #include "oqpi/threading/interface_thread.hpp" 7 | // Platform specific implementations 8 | #if OQPI_PLATFORM_WIN 9 | # include "oqpi/threading/win_thread.hpp" 10 | #elif OQPI_PLATFORM_POSIX 11 | # include "oqpi/threading/posix_thread.hpp" 12 | #else 13 | # error No thread implementation defined for the current platform 14 | #endif 15 | 16 | namespace oqpi { 17 | 18 | template typename _Layer = empty_layer> 19 | using thread_interface = itfc::thread; 20 | 21 | #ifdef OQPI_USE_DEFAULT 22 | using thread = thread_interface<>; 23 | #endif 24 | 25 | } -------------------------------------------------------------------------------- /include/oqpi/threading/thread_attributes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace oqpi { 8 | 9 | //---------------------------------------------------------------------------------------------- 10 | enum class thread_priority 11 | { 12 | lowest, 13 | below_normal, 14 | normal, 15 | above_normal, 16 | highest, 17 | time_critical, 18 | 19 | count 20 | }; 21 | //---------------------------------------------------------------------------------------------- 22 | 23 | //---------------------------------------------------------------------------------------------- 24 | enum core_affinity : uint32_t 25 | { 26 | core0 = 1, 27 | core1 = core0 << 1, 28 | core2 = core1 << 1, 29 | core3 = core2 << 1, 30 | core4 = core3 << 1, 31 | core5 = core4 << 1, 32 | core6 = core5 << 1, 33 | core7 = core6 << 1, 34 | 35 | all_cores = (std::numeric_limits::max)() 36 | }; 37 | //---------------------------------------------------------------------------------------------- 38 | 39 | //---------------------------------------------------------------------------------------------- 40 | struct thread_attributes 41 | { 42 | // Thread's name that will appear in various debug tools 43 | std::string name_; 44 | // The maximal stack size of the thread, 0 will use the system's default value 45 | uint32_t stackSize_; 46 | // Specifies which cores this thread is allowed to run on. 47 | core_affinity coreAffinityMask_; 48 | // The higher the priority the bigger the time slices this thread will be given in the 49 | // underlying OS scheduler. 50 | thread_priority priority_; 51 | 52 | // Constructor with default values, the name should always be specified 53 | thread_attributes 54 | ( 55 | const std::string name, 56 | uint32_t stackSize = 0u, 57 | core_affinity coreAffinityMask = core_affinity::all_cores, 58 | thread_priority priority = thread_priority::normal 59 | ) 60 | : name_ (name) 61 | , stackSize_ (stackSize) 62 | , coreAffinityMask_ (coreAffinityMask) 63 | , priority_ (priority) 64 | {} 65 | }; 66 | //---------------------------------------------------------------------------------------------- 67 | 68 | } 69 | -------------------------------------------------------------------------------- /tests/event_tests.hpp: -------------------------------------------------------------------------------- 1 | 2 | TEST_CASE("Events.", "[event]") 3 | { 4 | SECTION("Local events.") 5 | { 6 | SECTION("Auto Reset.") 7 | { 8 | auto autoResetEvent = oqpi::auto_reset_event(); 9 | REQUIRE(autoResetEvent.isValid()); 10 | 11 | oqpi::thread("autoResetThread", [&autoResetEvent]() 12 | { 13 | autoResetEvent.notify(); 14 | }).join(); 15 | 16 | auto notified = autoResetEvent.waitFor(std::chrono::seconds(10)); 17 | REQUIRE(notified); 18 | 19 | // As this is an auto reset event it should be automatically set to the non_signalled state after being signaled and waited upon once. 20 | notified = autoResetEvent.waitFor(std::chrono::microseconds(1)); 21 | REQUIRE(!notified); 22 | } 23 | SECTION("Manual Reset.") 24 | { 25 | auto manualResetEvent = oqpi::manual_reset_event(); 26 | REQUIRE(manualResetEvent.isValid()); 27 | 28 | oqpi::thread("manualResetThread", [&manualResetEvent]() 29 | { 30 | manualResetEvent.notify(); 31 | }).join(); 32 | 33 | auto notified = manualResetEvent.waitFor(std::chrono::seconds(10)); 34 | REQUIRE(notified); 35 | 36 | // As this is a manual reset event, it should only be set to the nonsignaled state after an explicit call to reset(). 37 | // Therefore in the call below it should be notified. 38 | notified = manualResetEvent.waitFor(std::chrono::microseconds(1)); 39 | REQUIRE(notified); 40 | 41 | manualResetEvent.reset(); 42 | notified = manualResetEvent.waitFor(std::chrono::microseconds(1)); 43 | REQUIRE(!notified); 44 | } 45 | } 46 | 47 | SECTION("Global events.") 48 | { 49 | SECTION("Auto Reset.") 50 | { 51 | auto autoResetEvent 52 | = oqpi::global_auto_reset_event("Global\\oqpiTestEvent", oqpi::sync_object_creation_options::create_if_nonexistent); 53 | REQUIRE(autoResetEvent.isValid()); 54 | 55 | auto autoResetEvent2 56 | = oqpi::global_auto_reset_event("Global\\oqpiTestEvent", oqpi::sync_object_creation_options::create_if_nonexistent); 57 | REQUIRE(!autoResetEvent2.isValid()); 58 | 59 | autoResetEvent2 60 | = oqpi::global_auto_reset_event("Global\\oqpiTestEvent", oqpi::sync_object_creation_options::open_existing); 61 | REQUIRE(autoResetEvent2.isValid()); 62 | 63 | autoResetEvent.notify(); 64 | auto notified = autoResetEvent2.waitFor(std::chrono::seconds(10)); 65 | 66 | REQUIRE(notified); 67 | 68 | notified = autoResetEvent.waitFor(std::chrono::microseconds(1)); 69 | REQUIRE(!notified); 70 | } 71 | SECTION("Manual Reset.") 72 | { 73 | auto manualResetEvent 74 | = oqpi::global_manual_reset_event("Global\\oqpiTestEvent", oqpi::sync_object_creation_options::create_if_nonexistent); 75 | REQUIRE(manualResetEvent.isValid()); 76 | 77 | auto manualResetEvent2 78 | = oqpi::global_manual_reset_event("Global\\oqpiTestEvent", oqpi::sync_object_creation_options::create_if_nonexistent); 79 | REQUIRE(!manualResetEvent2.isValid()); 80 | 81 | manualResetEvent2 82 | = oqpi::global_manual_reset_event("Global\\oqpiTestEvent", oqpi::sync_object_creation_options::open_existing); 83 | REQUIRE(manualResetEvent2.isValid()); 84 | 85 | manualResetEvent.notify(); 86 | auto notified = manualResetEvent2.waitFor(std::chrono::seconds(10)); 87 | 88 | REQUIRE(notified); 89 | 90 | notified = manualResetEvent.waitFor(std::chrono::microseconds(1)); 91 | REQUIRE(notified); 92 | 93 | manualResetEvent2.reset(); 94 | notified = manualResetEvent.waitFor(std::chrono::microseconds(1)); 95 | 96 | REQUIRE(!notified); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /tests/mutex_tests.hpp: -------------------------------------------------------------------------------- 1 | 2 | TEST_CASE("Mutexes.", "[mutex]") 3 | { 4 | SECTION("Global Mutex.") 5 | { 6 | auto mutex = oqpi::global_mutex("Global\\oqpiTestMutex", oqpi::sync_object_creation_options::open_existing); 7 | REQUIRE(!mutex.isValid()); 8 | 9 | mutex = oqpi::global_mutex("Global\\oqpiTestMutex", oqpi::sync_object_creation_options::create_if_nonexistent); 10 | REQUIRE(mutex.isValid()); 11 | 12 | auto mutex2 = oqpi::global_mutex("Global\\oqpiTestMutex", oqpi::sync_object_creation_options::open_existing); 13 | REQUIRE(mutex2.isValid()); 14 | 15 | mutex.unlock(); 16 | auto lockSucceeded = mutex2.tryLockFor(std::chrono::microseconds(1u)); 17 | REQUIRE(lockSucceeded); 18 | 19 | mutex2.unlock(); 20 | lockSucceeded = mutex.tryLockFor(std::chrono::microseconds(1u)); 21 | REQUIRE(lockSucceeded); 22 | 23 | // Test mutex destruction. 24 | { 25 | auto mutex3 = oqpi::global_mutex("Global\\oqpiTestMutex2", oqpi::sync_object_creation_options::create_if_nonexistent); 26 | REQUIRE(mutex3.isValid()); 27 | } 28 | 29 | mutex = oqpi::global_mutex("Global\\oqpiTestMutex2", oqpi::sync_object_creation_options::create_if_nonexistent); 30 | REQUIRE(mutex.isValid()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/oqpi_tests.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch_amalgamated.hpp" 3 | 4 | #define OQPI_USE_DEFAULT 5 | #include "oqpi.hpp" 6 | 7 | #include "test_utils.hpp" 8 | 9 | 10 | using namespace std::chrono_literals; 11 | 12 | #define PROFILE_TASKS (1) 13 | //-------------------------------------------------------------------------------------------------- 14 | // Define the toolkit we want to use 15 | #if PROFILE_TASKS 16 | using gc = oqpi::group_context_container; 17 | using tc = oqpi::task_context_container; 18 | using oqpi_tk = oqpi::helpers, gc, tc>; 19 | #else 20 | using oqpi_tk = oqpi::default_helpers; 21 | #endif 22 | //-------------------------------------------------------------------------------------------------- 23 | 24 | 25 | TEST_CASE("Setup.", "[cleanup]") 26 | { 27 | oqpi_tk::start_default_scheduler(); 28 | } 29 | 30 | #include "threading_tests.hpp" 31 | 32 | #include "scheduling_tests.hpp" 33 | 34 | #include "parallel_algorithms_tests.hpp" 35 | 36 | #include "event_tests.hpp" 37 | 38 | #include "mutex_tests.hpp" 39 | 40 | #include "semaphore_tests.hpp" 41 | 42 | //#include "sync_tests.hpp" 43 | 44 | TEST_CASE("Cleanup.", "[cleanup]") 45 | { 46 | oqpi_tk::stop_scheduler(); 47 | } 48 | -------------------------------------------------------------------------------- /tests/parallel_algorithms_tests.hpp: -------------------------------------------------------------------------------- 1 | 2 | //-------------------------------------------------------------------------------------------------- 3 | void test_parallel_for_task() 4 | { 5 | TEST_FUNC; 6 | 7 | const auto prio = oqpi::task_priority::normal; 8 | const auto partitioner = oqpi::simple_partitioner(gTaskCount, oqpi_tk::scheduler().workersCount(prio)); 9 | auto spParallelForGroup = oqpi_tk::make_parallel_for_task_group("FibonacciParallelForGroup", partitioner, prio, 10 | [](int32_t i) 11 | { 12 | volatile auto a = 0ull; 13 | a += fibonacci(gValue + a); 14 | a += i; 15 | }); 16 | oqpi_tk::schedule_task(oqpi::task_handle(spParallelForGroup)).wait(); 17 | } 18 | 19 | //-------------------------------------------------------------------------------------------------- 20 | void test_parallel_for() 21 | { 22 | TEST_FUNC; 23 | 24 | oqpi_tk::parallel_for("FibonacciParallelFor", gTaskCount, [](int32_t i) 25 | { 26 | volatile auto a = 0ull; 27 | a += fibonacci(gValue + a); 28 | a += i; 29 | }); 30 | } 31 | 32 | //-------------------------------------------------------------------------------------------------- 33 | void test_parallel_for_each() 34 | { 35 | TEST_FUNC; 36 | 37 | std::vector vec 38 | { 39 | "Lorem ipsum dolor", 40 | " sit amet, consectetur", 41 | " adipiscing elit.", 42 | "Nullam nulla sapien,", 43 | " mattis a egestas id,", 44 | " lobortis ut mauris.", 45 | "Proin consectetur finibus diam,", 46 | " eu maximus velit interdum sed.", 47 | "Quisque massa nibh, molestie vitae", 48 | " volutpat sed, lacinia non odio.", 49 | "Aenean dui enim, porttitor quis velit quis,", 50 | " lacinia viverra ex.", 51 | "Vivamus et libero sit amet orci consequat consectetur.", 52 | "Duis eu porttitor nunc,", 53 | " nec scelerisque mi.Cras efficitur lobortis elit, id", 54 | " scelerisque diam interdum ut.", 55 | "Quisque blandit sodales venenatis.", 56 | "Ut at purus congue dolor cursus posuere.", 57 | "Aliquam ac volutpat turpis, ac placerat nisl." 58 | }; 59 | const auto vecSize = int32_t(vec.size()); 60 | std::atomic count = 0; 61 | 62 | oqpi_tk::parallel_for_each("FibonacciParallelForEach", vec, 63 | [&count](const std::string &s) 64 | { 65 | uint64_t total = 0; 66 | for (auto c : s) 67 | { 68 | total += uint64_t(c) * 100; 69 | } 70 | count += fibonacci(total); 71 | }); 72 | } 73 | 74 | //-------------------------------------------------------------------------------------------------- 75 | void test_partitioners() 76 | { 77 | TEST_FUNC; 78 | 79 | std::vector vec 80 | { 81 | 56,96,63,25,84,20,54,86,45,89,32,44,58,99,85,32, 82 | 56985635,58975563,96583265,85452542,63254125,78965458,52365425,85212547,12568542,33556396,66996655,88888888,89658756,56325426,66999988,51122554, 83 | 236,862,966,666,888,544,887,211,544,877,555,445,845,215,548,655, 84 | 2366,4862,8966,5666,3888,1454,8287,2211,5644,8787,5955,4845,8845,2145,5248,6555 85 | }; 86 | const auto vecSize = int32_t(vec.size()); 87 | std::vector fib(vecSize); 88 | 89 | auto h = oqpi_tk::schedule_task("FibTransform", [&vec, &fib] 90 | { 91 | std::transform(vec.begin(), vec.end(), fib.begin(), [](uint64_t i) { return fibonacci(i); }); 92 | }); 93 | h.wait(); 94 | 95 | const auto prio = oqpi::task_priority::normal; 96 | const auto simplePartitioner = oqpi::simple_partitioner(vecSize, oqpi_tk::scheduler().workersCount(prio)); 97 | oqpi_tk::parallel_for_each("FibonacciParallelForEach", vec, simplePartitioner, prio, 98 | [](uint64_t &i) 99 | { 100 | volatile auto a = 0ull; 101 | a += fibonacci(i + a); 102 | }); 103 | 104 | const auto atomicPartitioner = oqpi::atomic_partitioner(vecSize, 1, oqpi_tk::scheduler().workersCount(prio)); 105 | oqpi_tk::parallel_for_each("FibonacciParallelForEach", vec, atomicPartitioner, prio, 106 | [](uint64_t &i) 107 | { 108 | volatile auto a = 0ull; 109 | a += fibonacci(i + a); 110 | }); 111 | } 112 | 113 | //-------------------------------------------------------------------------------------------------- 114 | void test_parallel_algorithms() 115 | { 116 | test_parallel_for(); 117 | 118 | test_parallel_for_task(); 119 | 120 | test_parallel_for_each(); 121 | 122 | test_partitioners(); 123 | } 124 | 125 | //-------------------------------------------------------------------------------------------------- 126 | TEST_CASE("Parallel algorithms (parallel for, partitioners).", "[scheduling]") 127 | { 128 | CHECK_NOTHROW(test_parallel_algorithms()); 129 | } 130 | -------------------------------------------------------------------------------- /tests/scheduling_tests.hpp: -------------------------------------------------------------------------------- 1 | 2 | //-------------------------------------------------------------------------------------------------- 3 | void test_unit_task_result() 4 | { 5 | TEST_FUNC; 6 | 7 | auto spTask = oqpi_tk::make_task("FibonacciReturnResult", fibonacci, gValue); 8 | oqpi_tk::schedule_task(spTask); 9 | std::cout << "Fibonacci(" << gValue << ") = " << spTask->waitForResult() << std::endl; 10 | } 11 | 12 | //-------------------------------------------------------------------------------------------------- 13 | void test_unit_task() 14 | { 15 | TEST_FUNC; 16 | 17 | auto spTask = oqpi_tk::make_task("FibonacciPerCore", oqpi::task_priority::normal, [] 18 | { 19 | for (int i = 0; i < gTaskCount; ++i) 20 | { 21 | volatile auto a = 0ull; 22 | a += fibonacci(gValue+a); 23 | } 24 | }); 25 | oqpi_tk::schedule_task(spTask).wait(); 26 | } 27 | 28 | //-------------------------------------------------------------------------------------------------- 29 | void test_multiple_unit_tasks() 30 | { 31 | TEST_FUNC; 32 | 33 | std::vector handles(gTaskCount); 34 | for (auto i = 0; i < gTaskCount; ++i) 35 | { 36 | handles[i] = oqpi_tk::schedule_task("Fibonacci_" + std::to_string(i), fibonacci, gValue); 37 | } 38 | for (auto &h : handles) 39 | { 40 | h.wait(); 41 | } 42 | } 43 | 44 | //-------------------------------------------------------------------------------------------------- 45 | void test_sequence_group() 46 | { 47 | TEST_FUNC; 48 | 49 | auto spSeq = oqpi_tk::make_sequence_group("Sequence"); 50 | for (auto i = 0; i < gTaskCount; ++i) 51 | { 52 | auto spTask = oqpi_tk::make_task_item("FibonacciSeq" + std::to_string(i), fibonacci, gValue); 53 | spSeq->addTask(spTask); 54 | } 55 | oqpi_tk::schedule_task(oqpi::task_handle(spSeq)).wait(); 56 | } 57 | 58 | //-------------------------------------------------------------------------------------------------- 59 | void test_parallel_group() 60 | { 61 | TEST_FUNC; 62 | 63 | auto spFork = oqpi_tk::make_parallel_group("Fork", oqpi::task_priority::normal, gTaskCount); 64 | for (auto i = 0; i < gTaskCount; ++i) 65 | { 66 | auto spTask = oqpi_tk::make_task_item("FibonacciFork" + std::to_string(i), fibonacci, gValue); 67 | spFork->addTask(spTask); 68 | } 69 | oqpi_tk::schedule_task(oqpi::task_handle(spFork)).wait(); 70 | } 71 | 72 | //-------------------------------------------------------------------------------------------------- 73 | void test_sequence_of_parallel_groups() 74 | { 75 | TEST_FUNC; 76 | 77 | auto spSeq = oqpi_tk::make_sequence_group("Sequence"); 78 | for (auto i = 0; i < gTaskCount; ++i) 79 | { 80 | auto spFork = oqpi_tk::make_parallel_group("Fork" + std::to_string(i), oqpi::task_priority::normal, gTaskCount); 81 | for (auto j = 0; j < gTaskCount; ++j) 82 | { 83 | auto spTask = oqpi_tk::make_task_item("Fibonacci_" + std::to_string(i*10+j), oqpi::task_priority::normal, fibonacci, gValue); 84 | spFork->addTask(spTask); 85 | } 86 | spSeq->addTask(oqpi::task_handle(spFork)); 87 | } 88 | oqpi_tk::schedule_task(oqpi::task_handle(spSeq)).wait(); 89 | } 90 | 91 | //-------------------------------------------------------------------------------------------------- 92 | void test_scheduling() 93 | { 94 | test_unit_task_result(); 95 | 96 | test_unit_task(); 97 | 98 | test_multiple_unit_tasks(); 99 | 100 | test_sequence_group(); 101 | 102 | test_parallel_group(); 103 | 104 | test_sequence_of_parallel_groups(); 105 | } 106 | 107 | //-------------------------------------------------------------------------------------------------- 108 | TEST_CASE("Scheduling (tasks, sequence groups, parallel groups).", "[scheduling]") 109 | { 110 | CHECK_NOTHROW(test_scheduling()); 111 | } 112 | -------------------------------------------------------------------------------- /tests/semaphore_tests.hpp: -------------------------------------------------------------------------------- 1 | 2 | TEST_CASE("Semaphores.", "[semaphore]") 3 | { 4 | SECTION("Local Semaphore.") 5 | { 6 | const auto initCount = 2u; 7 | const auto maxCount = 3u; 8 | 9 | auto semaphore = oqpi::semaphore(initCount, maxCount); 10 | REQUIRE(semaphore.isValid()); 11 | 12 | auto success = semaphore.tryWait(); // semaphoreCount = 1. 13 | REQUIRE(success); 14 | 15 | success = semaphore.tryWait(); // semaphoreCount = 0. 16 | REQUIRE(success); 17 | 18 | success = semaphore.tryWait(); // semaphoreCount = 0. 19 | REQUIRE(!success); 20 | 21 | semaphore.notifyOne(); // Semaphore count = 1. 22 | 23 | success = semaphore.tryWait(); // semaphoreCount = 0. 24 | REQUIRE(success); 25 | 26 | semaphore.notifyAll(); // semaphoreCount = 3. 27 | 28 | success = semaphore.tryWait() // semaphoreCount = 2. 29 | && semaphore.tryWait() // semaphoreCount = 1. 30 | && semaphore.tryWait(); // semaphoreCount = 0. 31 | 32 | REQUIRE(success); 33 | } 34 | SECTION("Global Semaphore.") 35 | { 36 | const auto initCount = 2u; 37 | const auto maxCount = 3u; 38 | 39 | auto semaphore = oqpi::global_semaphore("Global\\oqpiTestSemaphore", oqpi::sync_object_creation_options::open_existing); 40 | REQUIRE(!semaphore.isValid()); 41 | 42 | semaphore = oqpi::global_semaphore("Global\\oqpiTestSemaphore", oqpi::sync_object_creation_options::create_if_nonexistent, initCount, maxCount); 43 | REQUIRE(semaphore.isValid()); 44 | 45 | auto semaphore2 = oqpi::global_semaphore("Global\\oqpiTestSemaphore", oqpi::sync_object_creation_options::open_existing); 46 | REQUIRE(semaphore2.isValid()); 47 | 48 | REQUIRE(semaphore.getName() == semaphore2.getName()); 49 | 50 | auto success = semaphore.tryWait(); // semaphoreCount = 1. 51 | REQUIRE(success); 52 | 53 | success = semaphore2.tryWait(); // semaphoreCount = 0. 54 | REQUIRE(success); 55 | 56 | success = semaphore2.tryWait(); // semaphoreCount = 0. 57 | REQUIRE(!success); 58 | 59 | semaphore2.notifyOne(); // Semaphore count = 1. 60 | 61 | success = semaphore.tryWait(); // semaphoreCount = 0. 62 | REQUIRE(success); 63 | 64 | semaphore.notifyAll(); // semaphoreCount = 3. 65 | 66 | success = semaphore2.tryWait() // semaphoreCount = 2. 67 | && semaphore2.tryWait() // semaphoreCount = 1. 68 | && semaphore2.tryWait(); // semaphoreCount = 0. 69 | 70 | REQUIRE(success); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/sync_tests.hpp: -------------------------------------------------------------------------------- 1 | 2 | TEST_CASE("Sync.", "[sync]") 3 | { 4 | SECTION("wait_indefinitely_for_any") 5 | { 6 | // Make two events and two semaphores. Only the local semaphore is in a signaled state. 7 | // wait_indefinitely_for_any should return the index number of that semaphore i.e. 2. 8 | 9 | const auto autoResetEvent = oqpi::auto_reset_event(); 10 | 11 | const auto globalManualResetEvent 12 | = oqpi::global_manual_reset_event("Global\\oqpiTestEvent", oqpi::sync_object_creation_options::create_if_nonexistent); 13 | 14 | auto initCount = 1; 15 | 16 | const auto semaphore = oqpi::semaphore(initCount); 17 | 18 | initCount = 0; 19 | 20 | const auto globalSemaphore 21 | = oqpi::global_semaphore("Global\\oqpiTestSemaphore", oqpi::sync_object_creation_options::create_if_nonexistent, initCount); 22 | 23 | const auto result = oqpi::sync::wait_indefinitely_for_any(autoResetEvent, globalManualResetEvent, semaphore, globalSemaphore); 24 | 25 | REQUIRE(result == 2); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/test_utils.hpp: -------------------------------------------------------------------------------- 1 | #include "timer_contexts.hpp" 2 | 3 | //-------------------------------------------------------------------------------------------------- 4 | static constexpr auto gValue = 10000000ull; 5 | static const auto gTaskCount = int(oqpi::thread::hardware_concurrency()); 6 | //-------------------------------------------------------------------------------------------------- 7 | 8 | 9 | //-------------------------------------------------------------------------------------------------- 10 | uint64_t fibonacci(uint64_t n) 11 | { 12 | uint64_t a = 1, b = 1; 13 | for (uint64_t i = 3; i <= n; ++i) 14 | { 15 | uint64_t c = a + b; 16 | a = b; 17 | b = c; 18 | } 19 | return b; 20 | } 21 | //-------------------------------------------------------------------------------------------------- 22 | 23 | 24 | //-------------------------------------------------------------------------------------------------- 25 | struct test 26 | { 27 | test(const char *functionName) 28 | { 29 | std::cout << "-------------------------------------------------------------------" << std::endl; 30 | std::cout << functionName << std::endl; 31 | std::cout << "-------------------------------------------------------------------" << std::endl; 32 | } 33 | 34 | ~test() 35 | { 36 | timing_registry::get().dump(); 37 | timing_registry::get().reset(); 38 | std::cout << "-------------------------------------------------------------------" << std::endl; 39 | std::cout << std::endl << std::endl; 40 | } 41 | }; 42 | //-------------------------------------------------------------------------------------------------- 43 | #define TEST_FUNC test __t(__FUNCTION__) 44 | //-------------------------------------------------------------------------------------------------- 45 | -------------------------------------------------------------------------------- /tests/threading_tests.hpp: -------------------------------------------------------------------------------- 1 | 2 | //-------------------------------------------------------------------------------------------------- 3 | static oqpi::core_affinity coreNumberToAffinity(uint32_t coreNumber) 4 | { 5 | return static_cast(pow(2, coreNumber)); 6 | } 7 | 8 | //-------------------------------------------------------------------------------------------------- 9 | TEST_CASE("Threading.", "[threading]") 10 | { 11 | // oqpi::thread() will create a non-joinable thread. 12 | // If we try to detach or join it, it should simply issue a warning. 13 | CHECK(oqpi::thread().joinable() == false); 14 | 15 | const auto numLogicalCores = oqpi::thread::hardware_concurrency(); 16 | // I assume that there are least two cores to test with below. 17 | REQUIRE(numLogicalCores >= 2); 18 | 19 | auto coreAffinityMask = oqpi::core_affinity(oqpi::core0 | oqpi::core1); 20 | // The maximal stack size of the thread, 0 will use the system's default value 21 | constexpr auto stackSize = 0u; 22 | auto priority = oqpi::thread_priority::lowest; 23 | const auto ta = oqpi::thread_attributes("Thread", stackSize, coreAffinityMask, priority); 24 | 25 | auto t = oqpi::thread(ta, []() 26 | { 27 | fibonacci(gValue); 28 | }); 29 | 30 | CHECK(t.getCoreAffinityMask() == coreAffinityMask); 31 | CHECK(t.getPriority() == priority); 32 | 33 | auto coreNum = numLogicalCores - 1; 34 | coreAffinityMask = coreNumberToAffinity(coreNum); 35 | 36 | t.setCoreAffinityMask(coreAffinityMask); 37 | CHECK(t.getCoreAffinityMask() == coreAffinityMask); 38 | 39 | coreNum = numLogicalCores - 2; 40 | coreAffinityMask = coreNumberToAffinity(coreNum); 41 | 42 | t.setCoreAffinity(coreNum); 43 | CHECK(t.getCoreAffinityMask() == coreAffinityMask); 44 | 45 | priority = oqpi::thread_priority::above_normal; 46 | t.setPriority(priority); 47 | CHECK(t.getPriority() == priority); 48 | 49 | REQUIRE(t.joinable() == true); 50 | t.join(); 51 | } 52 | -------------------------------------------------------------------------------- /tests/timer_contexts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "oqpi.hpp" 6 | 7 | 8 | int64_t query_performance_counter_aux() 9 | { 10 | #if OQPI_PLATFORM_WIN 11 | LARGE_INTEGER li; 12 | QueryPerformanceCounter(&li); 13 | return li.QuadPart; 14 | 15 | #elif OQPI_PLATFORM_POSIX 16 | timespec time; 17 | clock_gettime(CLOCK_MONOTONIC_RAW, &time); 18 | return time.tv_nsec + time.tv_sec * 1e9; 19 | 20 | #endif 21 | } 22 | 23 | int64_t first_measure() 24 | { 25 | static const auto firstMeasure = query_performance_counter_aux(); 26 | return firstMeasure; 27 | } 28 | 29 | int64_t query_performance_counter() 30 | { 31 | first_measure(); 32 | const auto t = query_performance_counter_aux(); 33 | return t - first_measure(); 34 | } 35 | 36 | int64_t query_performance_frequency() 37 | { 38 | #if OQPI_PLATFORM_WIN 39 | LARGE_INTEGER li; 40 | QueryPerformanceFrequency(&li); 41 | return li.QuadPart; 42 | 43 | #elif OQPI_PLATFORM_POSIX 44 | return -1; 45 | 46 | #endif 47 | } 48 | 49 | double duration(int64_t s, int64_t e) 50 | { 51 | #if OQPI_PLATFORM_WIN 52 | static const auto F = query_performance_frequency(); 53 | auto dt = e - s; 54 | return (dt / (F * 1.0)) * 1000.0; 55 | 56 | #elif OQPI_PLATFORM_POSIX 57 | auto dt = e - s; 58 | return dt / 1e6; 59 | 60 | #endif 61 | } 62 | 63 | 64 | class timing_registry 65 | { 66 | private: 67 | struct task_info 68 | { 69 | std::string name; 70 | int64_t createdAt = 0; 71 | int64_t startedAt_ = 0; 72 | int64_t stoppedAt_ = 0; 73 | double duration_ = 0; 74 | int32_t startedOnCore_ = -1; 75 | int32_t stoppedOnCore_ = -1; 76 | std::vector children; 77 | bool isGroup = false; 78 | oqpi::task_uid parentUID = oqpi::invalid_task_uid; 79 | 80 | void print() const 81 | { 82 | std::string msg = name; 83 | msg += " took "; 84 | msg += std::to_string(duration_); 85 | msg += "ms, it started on core "; 86 | msg += std::to_string(startedOnCore_); 87 | msg += ", and stopped on core "; 88 | msg += std::to_string(stoppedOnCore_); 89 | msg += "\n"; 90 | std::cout << msg; 91 | } 92 | }; 93 | 94 | public: 95 | static timing_registry& get() 96 | { 97 | static timing_registry instance; 98 | return instance; 99 | } 100 | 101 | void registerTask(uint64_t uid, const std::string &name) 102 | { 103 | auto t = query_performance_counter(); 104 | std::lock_guard __l(m); 105 | auto &h = tasks[uid]; 106 | h.name = name; 107 | h.createdAt = t; 108 | } 109 | 110 | void unregisterTask(uint64_t uid) 111 | { 112 | std::lock_guard __l(m); 113 | tasks.erase(uid); 114 | } 115 | 116 | void startTask(uint64_t uid) 117 | { 118 | auto t = query_performance_counter(); 119 | std::lock_guard __l(m); 120 | //oqpi_check(tasks.find(name) == tasks.end()); 121 | auto &h = tasks[uid]; 122 | h.startedOnCore_ = oqpi::this_thread::get_current_core(); 123 | h.startedAt_ = t; 124 | } 125 | 126 | void endTask(uint64_t uid) 127 | { 128 | auto t = query_performance_counter(); 129 | std::lock_guard __l(m); 130 | auto it = tasks.find(uid); 131 | //oqpi_check(it != tasks.end()); 132 | it->second.stoppedAt_ = t; 133 | it->second.duration_ = duration(it->second.startedAt_, it->second.stoppedAt_); 134 | it->second.stoppedOnCore_ = oqpi::this_thread::get_current_core(); 135 | } 136 | 137 | void addToGroup(uint64_t guid, oqpi::task_handle hTask) 138 | { 139 | std::lock_guard __l(m); 140 | // Flag the task as having a parent 141 | auto tit = tasks.find(hTask.getUID()); 142 | tit->second.parentUID = guid; 143 | // Add this task to the parent 144 | auto git = tasks.find(guid); 145 | git->second.children.emplace_back(std::move(hTask)); 146 | git->second.isGroup = true; 147 | } 148 | 149 | void dump() 150 | { 151 | std::vector childrenToDelete; 152 | { 153 | std::lock_guard __l(m); 154 | std::vector infos; 155 | for (auto &p : tasks) 156 | { 157 | if (p.second.parentUID == oqpi::invalid_task_uid) 158 | infos.push_back(&p.second); 159 | } 160 | print(infos, 0, childrenToDelete); 161 | } 162 | childrenToDelete.clear(); 163 | } 164 | 165 | void print(std::vector &infos, int depth, std::vector &childrenToDelete) 166 | { 167 | for (auto pti : infos) 168 | { 169 | auto &ti = (*pti); 170 | auto d = depth; 171 | while (d--) std::cout << " "; 172 | 173 | ti.print(); 174 | 175 | if (!ti.children.empty()) 176 | { 177 | double totalDuration = 0.0; 178 | auto children = collectChildren(ti.children, totalDuration); 179 | print(children, depth + 1, childrenToDelete); 180 | 181 | d = depth; 182 | while (d--) std::cout << " "; 183 | std::cout 184 | << "End of " 185 | << ti.name 186 | << " with " 187 | << ti.duration_ 188 | << "ms (total accumulated time of tasks = " 189 | << totalDuration 190 | << "ms, "; 191 | if (totalDuration < ti.duration_) 192 | { 193 | std::cout 194 | << "that is an overhead of " 195 | << ti.duration_ - totalDuration 196 | << "ms for group management)"; 197 | } 198 | else 199 | { 200 | std::cout 201 | << "the group represent " 202 | << ti.duration_ * 100.0 / totalDuration 203 | << "% of the total duration)"; 204 | } 205 | std::cout << std::endl; 206 | } 207 | 208 | for (auto &i : ti.children) 209 | { 210 | childrenToDelete.emplace_back(std::move(i)); 211 | } 212 | //ti.children.clear(); 213 | } 214 | } 215 | 216 | std::vector collectChildren(const std::vector &children, double &totalDuration) 217 | { 218 | std::vector infos; 219 | infos.reserve(children.size()); 220 | totalDuration = 0; 221 | for (auto &c : children) 222 | { 223 | infos.push_back(&tasks.find(c.getUID())->second); 224 | totalDuration += infos.back()->duration_; 225 | } 226 | return infos; 227 | } 228 | 229 | void reset() 230 | { 231 | tasks.clear(); 232 | } 233 | 234 | private: 235 | std::recursive_mutex m; 236 | std::unordered_map tasks; 237 | }; 238 | 239 | class timer_task_context 240 | : public oqpi::task_context_base 241 | { 242 | public: 243 | timer_task_context(oqpi::task_base *pOwner, const std::string &name) 244 | : oqpi::task_context_base(pOwner, name) 245 | { 246 | timing_registry::get().registerTask(pOwner->getUID(), name); 247 | } 248 | 249 | ~timer_task_context() 250 | { 251 | //timing_registry::get().unregisterTask(this->owner()->getUID()); 252 | } 253 | 254 | inline void onPreExecute() 255 | { 256 | timing_registry::get().startTask(this->owner()->getUID()); 257 | } 258 | 259 | inline void onPostExecute() 260 | { 261 | timing_registry::get().endTask(this->owner()->getUID()); 262 | } 263 | 264 | private: 265 | }; 266 | 267 | 268 | class timer_group_context 269 | : public oqpi::group_context_base 270 | { 271 | public: 272 | timer_group_context(oqpi::task_group_base *pOwner, const std::string &name) 273 | : oqpi::group_context_base(pOwner, name) 274 | { 275 | timing_registry::get().registerTask(pOwner->getUID(), name); 276 | } 277 | 278 | ~timer_group_context() 279 | { 280 | //timing_registry::get().unregisterTask(this->owner()->getUID()); 281 | } 282 | 283 | inline void onTaskAdded(const oqpi::task_handle &hTask) 284 | { 285 | timing_registry::get().addToGroup(this->owner()->getUID(), hTask); 286 | } 287 | 288 | inline void onPreExecute() 289 | { 290 | timing_registry::get().startTask(this->owner()->getUID()); 291 | } 292 | 293 | inline void onPostExecute() 294 | { 295 | timing_registry::get().endTask(this->owner()->getUID()); 296 | } 297 | }; 298 | 299 | 300 | namespace this_task { 301 | 302 | } 303 | --------------------------------------------------------------------------------