├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── README.md ├── include └── jobxx │ ├── _detail │ ├── job_impl.h │ ├── queue_impl.h │ └── task.h │ ├── concurrent_queue.h │ ├── context.h │ ├── delegate.h │ ├── job.h │ ├── park.h │ ├── predicate.h │ ├── queue.h │ └── spinlock.h └── source ├── context.cc ├── job.cc ├── park.cc ├── queue.cc └── tests.cc /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ 3 | .vs/ 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(jobxx) 4 | enable_testing() 5 | 6 | set(JOBXX_PUBLIC_HEADERS 7 | include/jobxx/concurrent_queue.h 8 | include/jobxx/context.h 9 | include/jobxx/delegate.h 10 | include/jobxx/job.h 11 | include/jobxx/spinlock.h 12 | include/jobxx/park.h 13 | include/jobxx/predicate.h 14 | include/jobxx/queue.h 15 | ) 16 | set(JOBXX_PRIVATE_HEADERS 17 | include/jobxx/_detail/job_impl.h 18 | include/jobxx/_detail/queue_impl.h 19 | include/jobxx/_detail/task.h 20 | ) 21 | set(JOBXX_SOURCES 22 | source/context.cc 23 | source/job.cc 24 | source/park.cc 25 | source/queue.cc 26 | ) 27 | set(JOBXX_TESTS 28 | source/tests.cc 29 | ) 30 | 31 | set(JOBXX_FILES ${JOBXX_PUBLIC_HEADERS} ${JOBXX_PRIVATE_HEADERS} ${JOBXX_SOURCES}) 32 | 33 | add_library(jobxx ${JOBXX_FILES}) 34 | target_include_directories(jobxx PUBLIC "include") 35 | source_group("Header Files\\_detail" FILES ${JOBXX_PRIVATE_HEADERS}) 36 | set_property(TARGET jobxx PROPERTY CXX_STANDARD 14) 37 | 38 | add_executable(jobxx_tests ${JOBXX_TESTS}) 39 | set_property(TARGET jobxx_tests PROPERTY CXX_STANDARD 14) 40 | target_link_libraries(jobxx_tests jobxx) 41 | add_test(jobxx_tests jobxx_tests) 42 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. 3 | "configurations": [ 4 | { 5 | "name": "Debug64", 6 | "generator": "Visual Studio 15 2017 Win64", 7 | "configurationType": "Debug", 8 | "buildRoot": "${projectDir}\\build\\win64", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "-m -v:minimal" 11 | }, 12 | { 13 | "name": "Release64", 14 | "generator": "Visual Studio 15 2017 Win64", 15 | "configurationType": "Release", 16 | "buildRoot": "${projectDir}\\build\\win64", 17 | "cmakeCommandArgs": "", 18 | "buildCommandArgs": "-m -v:minimal" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jobxx 2 | 3 | ## License 4 | 5 | Copyright (c) 2017 Sean Middleditch 6 | 7 | This is free and unencumbered software released into the public domain. 8 | 9 | Anyone is free to copy, modify, publish, use, compile, sell, or 10 | distribute this software, either in source code form or as a compiled 11 | binary, for any purpose, commercial or non-commercial, and by any 12 | means. 13 | 14 | In jurisdictions that recognize copyright laws, the author or authors 15 | of this software dedicate any and all copyright interest in the 16 | software to the public domain. We make this dedication for the benefit 17 | of the public at large and to the detriment of our heirs and 18 | successors. We intend this dedication to be an overt act of 19 | relinquishment in perpetuity of all present and future rights to this 20 | software under copyright law. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 26 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 27 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | For more information, please refer to 31 | 32 | ## Summary 33 | 34 | Simple task and job scheduling library for C++. The library aims for 35 | reasonably good performance, low overhead, light memory usage, and for 36 | suitability for games and soft-real-time uses. 37 | 38 | ## Documentation 39 | 40 | ### Design 41 | 42 | jobxx aims to be as light-weight as possible while retaining strong 43 | performance. To this end, the basic primitives of the library are 44 | few and focused on minimalism. 45 | 46 | The core concepts of jobxx are *jobs*, *tasks*, *queues*, and *threads*. 47 | Of the four, jobxx only directly represents the first three; threads 48 | are provided by `std::thread` or the application. 49 | 50 | The *task* is the lowest-level primitive of the core concepts. A task 51 | represents a unit of work. Tasks have no return values nor error states. 52 | If applications wish to communicate results from a task, they must use 53 | an external mechanism such as `std::future`. jobxx tasks are best suited 54 | for small discrete chunks of work with no failure state or individual 55 | results, though of course having a task mutate some shared state (with 56 | the appropriate care for thread-safety) is a common use case. For instance, 57 | spawning a number of tasks to mutate a large array - where each task 58 | operates on a distinct subrange of the array - is an excellent case. 59 | 60 | A *job* is a collection of tasks. jobxx allows users to create a job 61 | that spawns 0 or more tasks. The job maintains a completion state which 62 | is unset while the job has 1 or more tasks that have not yet been 63 | completed. Tasks spawned by a job may themselves spawn more tasks as 64 | part of the same job, allowing a job to represent the completion state 65 | of an entire tree of tasks, sub-tasks, and continuations. An application 66 | can either poll a job's completion state or block on its completion, 67 | working on tasks (if available) while waiting, or putting the thread to 68 | sleep until the job is ready. 69 | 70 | Scheduling tasks is performed by a *queue*. A queue is essentially 71 | a list of tasks that have been spawned and are ready to execute. Any 72 | number of threads may poll a queue for tasks to execute. Additionally, 73 | queues contain a mechanism for *parking* threads; this is a way for 74 | a thread to sleep/block until a particular queue has work available. The 75 | parking approach allows for task creation to be efficient (no need to 76 | signal the OS if there are no sleeping threads) and is extensible for 77 | future needs, such as parking a thread on a job until it completes (not 78 | yet supported). 79 | 80 | ### API 81 | 82 | The two primary points of the api are `jobxx::queue` and `jobxx::job`. 83 | 84 | #### `jobxx::queue` 85 | 86 | A `jobxx::queue` is used to spawn tasks, execute spawned tasks, to 87 | create `job` instances, and to wait for jobs to complete. 88 | 89 | ##### `queue::spawn_task(delegate work) -> void` 90 | 91 | Create a new task encapsulating the `work` to be performed. The task 92 | is put into a pending task queue and will be executed when a thread 93 | calls `queue::work_one`. 94 | 95 | ##### `queue::create_job(initializer : (context&) -> void) -> job` 96 | 97 | Creates a new `job` instance and then invokes `initializer` with 98 | a `context` object. Tasks spawned via this context will be added 99 | to the returned `job` as child tasks. 100 | 101 | #### `jobxx::job` 102 | 103 | A `jobxx::job` represents the completion state of a set of tasks. 104 | The job can be queried to see if all tasks spawned for the job have 105 | been fully executed. 106 | 107 | ##### `job::complete() const -> bool` 108 | 109 | Returns `true` if there are no outstanding tasks associated with the 110 | job. 111 | 112 | ##### `job::operator bool() const` 113 | 114 | Same as `job::complete`. 115 | 116 | #### `jobxx::context` 117 | 118 | A context allows for spawning tasks as part of a `job`. 119 | 120 | ##### `context::spawn_task(delegate work) -> void` 121 | 122 | As `queue::spawn_task`, except that the spawned task will be associated 123 | with the context's job. 124 | 125 | #### `jobxx::delegate` 126 | 127 | A `delegate` is very similar to `std::function` with two primary 128 | differences. First, a `delegate` is guaranteed to never allocate. It is 129 | a compile-time error to attempt to store a function object (or lambda, or 130 | other invokable) into a `delegate` if it is too large or overly aligned. 131 | 132 | Second, a `delegate` can wrap an invokable with one of two different 133 | potential signatures: `() -> void` or `(context&) -> void`. This allows 134 | for convenience when needing to construct a task which has no need for a 135 | `context` while still allowing for tasks which do need a `context`. 136 | 137 | Note: the current incarnation of `delegate` only works for function 138 | objects which are trivially move-constructible and trivially destructible. 139 | This limitation is planned to be lifted in future releases. 140 | 141 | ##### `delegate::delegate(function: () -> void)` 142 | 143 | Constructs a delegate wrapping `function`. 144 | 145 | ##### `delegate::delegate(function: (context&) -> void)` 146 | 147 | Constructs a delegate wrapping `function`. 148 | 149 | ##### `delegate::operator bool() const` 150 | 151 | Returns `true` if the delegate has been created with a function, or `false` 152 | for a default-constructed (empty) delegate. 153 | 154 | ##### `delegate::operator()(ctx: context&) -> void` 155 | 156 | Executes the stored function, passing `ctx` to it if the stored function 157 | takes a `context&` parameter. It is undefined behavior to call this 158 | operator on a `delegate` with no stored function. 159 | 160 | #### `jobxx::predicate` 161 | 162 | A `predicate` is simple wrapper for a function reference of signature 163 | `() -> bool`. Note that it is only a _reference_ type, meaning that it 164 | does not take ownership of a function object used to construct it. 165 | 166 | ##### `predicate::predicate(pred: () -> bool)` 167 | 168 | Creates a predicate wrapping the given `pred` invokable. 169 | 170 | ##### `predicate::operator bool() const` 171 | 172 | Returns `true` if the predicate was constructed with a function, or `false` 173 | if the predicate has no function reference. 174 | 175 | ##### `predicate::operator()() -> bool` 176 | 177 | Invokes the reference function and returns its result. It is undefined 178 | behavior to call this operator on a `predicate` with no stored 179 | function reference. 180 | 181 | -------------------------------------------------------------------------------- /include/jobxx/_detail/job_impl.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_DETAIL_JOB_H) 32 | #define _guard_JOBXX_DETAIL_JOB_H 33 | #pragma once 34 | 35 | #include "jobxx/park.h" 36 | #include 37 | 38 | namespace jobxx 39 | { 40 | namespace _detail 41 | { 42 | 43 | struct job_impl 44 | { 45 | std::atomic refs = 1; 46 | std::atomic tasks = 0; 47 | park waiting; 48 | }; 49 | 50 | } 51 | } 52 | 53 | #endif // defined(_guard_JOBXX_DETAIL_JOB_H) 54 | -------------------------------------------------------------------------------- /include/jobxx/_detail/queue_impl.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_DETAIL_QUEUE_H) 32 | #define _guard_JOBXX_DETAIL_QUEUE_H 33 | #pragma once 34 | 35 | #include "jobxx/delegate.h" 36 | #include "jobxx/concurrent_queue.h" 37 | #include "jobxx/park.h" 38 | #include 39 | 40 | namespace jobxx 41 | { 42 | 43 | enum class spawn_result; 44 | 45 | namespace _detail 46 | { 47 | 48 | struct job_impl; 49 | struct task; 50 | 51 | struct queue_impl 52 | { 53 | spawn_result spawn_task(delegate work, _detail::job_impl* parent); 54 | _detail::task* pull_task(); 55 | void execute(_detail::task* item); 56 | 57 | concurrent_queue<_detail::task*> tasks; 58 | park waiting; 59 | std::atomic closed = false; 60 | }; 61 | 62 | } 63 | 64 | } 65 | 66 | #endif // defined(_guard_JOBXX_DETAIL_QUEUE_H) 67 | -------------------------------------------------------------------------------- /include/jobxx/_detail/task.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_DETAIL_TASK_H) 32 | #define _guard_JOBXX_DETAIL_TASK_H 33 | #pragma once 34 | 35 | #include "jobxx/delegate.h" 36 | 37 | namespace jobxx 38 | { 39 | 40 | namespace _detail 41 | { 42 | 43 | struct job_impl; 44 | 45 | struct task 46 | { 47 | delegate work; 48 | _detail::job_impl* parent = nullptr; 49 | }; 50 | 51 | } 52 | } 53 | 54 | #endif // defined(_guard_JOBXX_DETAIL_TASK_H) 55 | -------------------------------------------------------------------------------- /include/jobxx/concurrent_queue.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_CONCURRENT_QUEUE_H) 32 | #define _guard_JOBXX_CONCURRENT_QUEUE_H 33 | #pragma once 34 | 35 | #include 36 | #include 37 | 38 | namespace jobxx 39 | { 40 | 41 | template 42 | class concurrent_queue 43 | { 44 | public: 45 | using value_type = Value; 46 | 47 | template inline void push_back(InsertValue&& task); 48 | inline bool pop_front(value_type& out); 49 | inline bool maybe_empty() const; 50 | 51 | private: 52 | // FIXME: temporary "just works" data-structure to be 53 | // replaced by "lock-free" structure 54 | mutable std::mutex _lock; 55 | std::deque _queue; 56 | }; 57 | 58 | template 59 | template 60 | void concurrent_queue::push_back(InsertValue&& task) 61 | { 62 | std::lock_guard _(_lock); 63 | _queue.push_back(std::forward(task)); 64 | } 65 | 66 | template 67 | bool concurrent_queue::pop_front(value_type& out) 68 | { 69 | std::lock_guard _(_lock); 70 | if (!_queue.empty()) 71 | { 72 | out = std::move(_queue.front()); 73 | _queue.pop_front(); 74 | return true; 75 | } 76 | else 77 | { 78 | return false; 79 | } 80 | } 81 | 82 | template 83 | bool concurrent_queue::maybe_empty() const 84 | { 85 | std::lock_guard _(_lock); 86 | return _queue.empty(); 87 | } 88 | 89 | } 90 | 91 | #endif // defined(_guard_JOBXX_CONCURRENT_QUEUE_H) 92 | -------------------------------------------------------------------------------- /include/jobxx/context.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_CONTEXT_H) 32 | #define _guard_JOBXX_CONTEXT_H 33 | #pragma once 34 | 35 | #include "delegate.h" 36 | 37 | namespace jobxx 38 | { 39 | 40 | namespace _detail 41 | { 42 | struct job_impl; 43 | struct queue_impl; 44 | } 45 | 46 | enum class spawn_result; 47 | 48 | class context 49 | { 50 | public: 51 | explicit context(_detail::queue_impl& queue, _detail::job_impl* parent) : _queue(queue), _job(parent) {} 52 | 53 | context(context const&) = delete; 54 | context& operator=(context const&) = delete; 55 | 56 | spawn_result spawn_task(delegate&& work); 57 | 58 | private: 59 | _detail::queue_impl& _queue; 60 | _detail::job_impl* _job = nullptr; 61 | }; 62 | 63 | } 64 | 65 | #endif // defined(_guard_JOBXX_CONTEXT_H) 66 | -------------------------------------------------------------------------------- /include/jobxx/delegate.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_DELEGATE_H) 32 | #define _guard_JOBXX_DELEGATE_H 33 | #pragma once 34 | 35 | #include 36 | #include 37 | 38 | namespace jobxx 39 | { 40 | 41 | class context; 42 | 43 | namespace _detail 44 | { 45 | template > 46 | struct takes_context : std::false_type {}; 47 | 48 | template 49 | struct takes_context()(std::declval()))>> : std::true_type {}; 50 | 51 | template 52 | constexpr bool takes_context_v = takes_context(); 53 | } 54 | 55 | class delegate 56 | { 57 | public: 58 | static constexpr int max_size = sizeof(void*) * 3; 59 | static constexpr int max_alignment = alignof(double); 60 | 61 | delegate() = default; 62 | 63 | delegate(delegate&& rhs) = default; 64 | delegate& operator=(delegate&& rhs) = delete; 65 | 66 | template /*implicit*/ delegate(FunctionT&& func) { _assign(std::forward(func)); } 67 | 68 | explicit operator bool() const { return _thunk != nullptr; } 69 | 70 | void operator()(context& ctx) { _thunk(&_storage, ctx); } 71 | 72 | private: 73 | template static auto _invoke(void* storage, context& ctx) -> std::enable_if_t<_detail::takes_context_v>; 74 | template static auto _invoke(void* storage, context&) -> std::enable_if_t>; 75 | 76 | template inline void _assign(FunctionT&& func); 77 | 78 | void(*_thunk)(void*, context& ctx) = nullptr; 79 | std::aligned_storage_t _storage; 80 | }; 81 | 82 | template 83 | auto delegate::_invoke(void* storage, context& ctx) -> std::enable_if_t<_detail::takes_context_v> 84 | { 85 | (*static_cast(storage))(ctx); 86 | } 87 | 88 | template 89 | auto delegate::_invoke(void* storage, context&) -> std::enable_if_t> 90 | { 91 | (*static_cast(storage))(); 92 | } 93 | 94 | template 95 | void delegate::_assign(FunctionT&& func) 96 | { 97 | using func_type = std::remove_reference_t; 98 | 99 | static_assert(sizeof(func_type) <= max_size, "function too large for jobxx::delegate"); 100 | static_assert(alignof(func_type) <= max_alignment, "function over-aligned for jobxx::delegate"); 101 | static_assert(std::is_trivially_move_constructible_v, "function not a trivially move-constructible as required by jobxx::delegate"); 102 | static_assert(std::is_trivially_destructible_v, "function not a trivially destructible as required by jobxx::delegate"); 103 | 104 | _thunk = &_invoke; 105 | new (&_storage) func_type(std::forward(func)); 106 | } 107 | 108 | } 109 | 110 | #endif // defined(_guard_JOBXX_DELEGATE_H) 111 | -------------------------------------------------------------------------------- /include/jobxx/job.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_JOB_H) 32 | #define _guard_JOBXX_JOB_H 33 | #pragma once 34 | 35 | namespace jobxx 36 | { 37 | 38 | namespace _detail { struct job_impl; } 39 | class queue; 40 | 41 | class job 42 | { 43 | public: 44 | job() = default; 45 | ~job(); 46 | 47 | // note this does not increment refs! 48 | explicit job(_detail::job_impl* impl) : _impl(impl) {} 49 | 50 | job(job&& rhs) : _impl(rhs._impl) { rhs._impl = nullptr; } 51 | job& operator=(job&& rhs); 52 | 53 | bool complete() const; 54 | explicit operator bool() const { return complete(); } 55 | 56 | private: 57 | _detail::job_impl* _impl = nullptr; 58 | 59 | friend queue; 60 | }; 61 | 62 | } 63 | 64 | #endif // defined(_guard_JOBXX_JOB_H) 65 | -------------------------------------------------------------------------------- /include/jobxx/park.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_PARK_H) 32 | #define _guard_JOBXX_PARK_H 33 | #pragma once 34 | 35 | #include "spinlock.h" 36 | #include "predicate.h" 37 | 38 | namespace jobxx 39 | { 40 | 41 | enum class park_result 42 | { 43 | failure = -1, 44 | first = 0, 45 | second = 1 46 | }; 47 | 48 | class park 49 | { 50 | public: 51 | park() = default; 52 | ~park() { unpark_all(); } 53 | 54 | park(park const&) = delete; 55 | park& operator=(park const&) = delete; 56 | 57 | park_result park_until(predicate pred) { return _park(this, pred); } 58 | static park_result park_until(park& first, predicate first_pred, park& second, predicate second_pred) { return _park(&first, first_pred, &second, second_pred); } 59 | 60 | bool unpark_one(); 61 | void unpark_all(); 62 | 63 | private: 64 | struct thread_state; 65 | struct parked_node 66 | { 67 | thread_state* _thread = nullptr; 68 | parked_node* _next = this; 69 | parked_node* _prev = this; 70 | int _id = 0; 71 | }; 72 | 73 | static park_result _park(park* first, predicate first_pred, park* second = nullptr, predicate second_pred = predicate()); 74 | 75 | bool _unpark(thread_state& thread); 76 | void _link(parked_node& node); 77 | void _unlink(parked_node& node); 78 | 79 | spinlock _lock; 80 | parked_node _parked; 81 | }; 82 | 83 | } 84 | 85 | #endif // defined(_guard_JOBXX_PARK_H) 86 | -------------------------------------------------------------------------------- /include/jobxx/predicate.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_PREDICATE_H) 32 | #define _guard_JOBXX_PREDICATE_H 33 | #pragma once 34 | 35 | #include 36 | 37 | namespace jobxx 38 | { 39 | 40 | class predicate 41 | { 42 | public: 43 | predicate() = default; 44 | 45 | template >, predicate>>> 46 | /*implicit*/ predicate(FunctionT&& func) { _assign(std::forward(func)); } 47 | 48 | explicit operator bool() const { return _thunk != nullptr; } 49 | 50 | bool operator()() { return _thunk(_view); } 51 | 52 | private: 53 | template inline void _assign(FunctionT&& func); 54 | 55 | bool(*_thunk)(void*) = nullptr; 56 | void* _view = nullptr; 57 | }; 58 | 59 | template 60 | void predicate::_assign(FunctionT&& func) 61 | { 62 | using func_type = std::remove_reference_t; 63 | _thunk = [](void* view){ return (*static_cast(view))(); }; 64 | _view = &func; 65 | } 66 | 67 | } 68 | 69 | #endif // defined(_guard_JOBXX_PREDICATE_H) 70 | -------------------------------------------------------------------------------- /include/jobxx/queue.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_QUEUE_H) 32 | #define _guard_JOBXX_QUEUE_H 33 | #pragma once 34 | 35 | #include "delegate.h" 36 | #include "job.h" 37 | #include "context.h" 38 | #include 39 | 40 | namespace jobxx 41 | { 42 | 43 | namespace _detail { struct queue_impl; } 44 | 45 | enum class spawn_result 46 | { 47 | success, 48 | queue_full, 49 | empty_function, 50 | queue_closed 51 | }; 52 | 53 | class queue 54 | { 55 | public: 56 | queue(); 57 | ~queue(); 58 | 59 | queue(queue const&) = delete; 60 | queue& operator=(queue const&) = delete; 61 | 62 | template job create_job(InitFunctionT&& initializer); 63 | spawn_result spawn_task(delegate&& work); 64 | 65 | void wait_job_actively(job const& awaited); 66 | 67 | bool work_one(); 68 | void work_all(); 69 | void work_forever(); 70 | 71 | void close(); 72 | 73 | private: 74 | _detail::job_impl* _create_job(); 75 | 76 | _detail::queue_impl* _impl = nullptr; 77 | }; 78 | 79 | template 80 | job queue::create_job(InitFunctionT&& initializer) 81 | { 82 | _detail::job_impl* job_impl = _create_job(); 83 | context ctx(*_impl, job_impl); 84 | initializer(ctx); 85 | return job(job_impl); 86 | } 87 | 88 | } 89 | 90 | #endif // defined(_guard_JOBXX_QUEUE_H) 91 | -------------------------------------------------------------------------------- /include/jobxx/spinlock.h: -------------------------------------------------------------------------------- 1 | // jobxx - C++ lightweight task library. 2 | // 3 | // This is free and unencumbered software released into the public domain. 4 | // 5 | // Anyone is free to copy, modify, publish, use, compile, sell, or 6 | // distribute this software, either in source code form or as a compiled 7 | // binary, for any purpose, commercial or non - commercial, and by any 8 | // means. 9 | // 10 | // In jurisdictions that recognize copyright laws, the author or authors 11 | // of this software dedicate any and all copyright interest in the 12 | // software to the public domain. We make this dedication for the benefit 13 | // of the public at large and to the detriment of our heirs and 14 | // successors. We intend this dedication to be an overt act of 15 | // relinquishment in perpetuity of all present and future rights to this 16 | // software under copyright law. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | // OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | // For more information, please refer to 27 | // 28 | // Authors: 29 | // Sean Middleditch 30 | 31 | #if !defined(_guard_JOBXX_SPINLOCK_H) 32 | #define _guard_JOBXX_SPINLOCK_H 33 | #pragma once 34 | 35 | #include 36 | 37 | namespace jobxx 38 | { 39 | 40 | class spinlock 41 | { 42 | public: 43 | spinlock() = default; 44 | 45 | spinlock(spinlock const&) = delete; 46 | spinlock& operator=(spinlock const&) = delete; 47 | 48 | inline void lock(); 49 | inline void unlock(); 50 | 51 | private: 52 | std::atomic _flag = false; 53 | }; 54 | 55 | void spinlock::lock() 56 | { 57 | for (;;) 58 | { 59 | // spin waiting for the lock to be free. this spin is avoiding 60 | // invalidating the cacheline (since it's only reading). 61 | int spins = 0; 62 | while (_flag.load(std::memory_order_relaxed) == true) 63 | { 64 | // FIXME: there aren't any standard C++ ways to briefly relax the 65 | // CPU (e.g. Intel's RELAX instruction _mm_pause()) and I'm 66 | // not yet ready to start the CPU-specific optimizations. 67 | } 68 | 69 | // the lock is unlocked, so now we try to acquire it. another 70 | // thread may have already acquired it, though, so there's a 71 | // chance this will fail, and we'll need to spin more. 72 | if (_flag.exchange(true, std::memory_order_acquire) == /*old-value*/false) 73 | { 74 | // lock acquired 75 | break; 76 | } 77 | } 78 | } 79 | 80 | void spinlock::unlock() 81 | { 82 | _flag.store(false, std::memory_order_release); 83 | } 84 | 85 | } 86 | 87 | #endif // defined(_guard_JOBXX_SPINLOCK_H) 88 | -------------------------------------------------------------------------------- /source/context.cc: -------------------------------------------------------------------------------- 1 | // means. 2 | // 3 | // In jurisdictions that recognize copyright laws, the author or authors 4 | // of this software dedicate any and all copyright interest in the 5 | // software to the public domain. We make this dedication for the benefit 6 | // of the public at large and to the detriment of our heirs and 7 | // successors. We intend this dedication to be an overt act of 8 | // relinquishment in perpetuity of all present and future rights to this 9 | // software under copyright law. 10 | // 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 15 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 16 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 17 | // OTHER DEALINGS IN THE SOFTWARE. 18 | // 19 | // For more information, please refer to 20 | // 21 | // Authors: 22 | // Sean Middleditch 23 | 24 | #include "jobxx/context.h" 25 | #include "jobxx/_detail/queue_impl.h" 26 | 27 | auto jobxx::context::spawn_task(delegate&& work) -> spawn_result 28 | { 29 | return _queue.spawn_task(std::move(work), _job); 30 | } 31 | -------------------------------------------------------------------------------- /source/job.cc: -------------------------------------------------------------------------------- 1 | 2 | // jobxx - C++ lightweight task library. 3 | // 4 | // This is free and unencumbered software released into the public domain. 5 | // 6 | // Anyone is free to copy, modify, publish, use, compile, sell, or 7 | // distribute this software, either in source code form or as a compiled 8 | // binary, for any purpose, commercial or non - commercial, and by any 9 | // means. 10 | // 11 | // In jurisdictions that recognize copyright laws, the author or authors 12 | // of this software dedicate any and all copyright interest in the 13 | // software to the public domain. We make this dedication for the benefit 14 | // of the public at large and to the detriment of our heirs and 15 | // successors. We intend this dedication to be an overt act of 16 | // relinquishment in perpetuity of all present and future rights to this 17 | // software under copyright law. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | // OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | // For more information, please refer to 28 | // 29 | // Authors: 30 | // Sean Middleditch 31 | 32 | #include "jobxx/job.h" 33 | #include "jobxx/_detail/job_impl.h" 34 | 35 | jobxx::job::~job() 36 | { 37 | if (_impl != nullptr && 0 == --_impl->refs) 38 | { 39 | delete _impl; 40 | } 41 | } 42 | 43 | jobxx::job& jobxx::job::operator=(job&& rhs) 44 | { 45 | if (this != &rhs) 46 | { 47 | if (_impl != nullptr && 0 == --_impl->refs) 48 | { 49 | delete _impl; 50 | } 51 | 52 | _impl = rhs._impl; 53 | rhs._impl = nullptr; 54 | 55 | if (_impl != nullptr) 56 | { 57 | ++_impl->refs; 58 | } 59 | } 60 | 61 | return *this; 62 | } 63 | 64 | bool jobxx::job::complete() const 65 | { 66 | return _impl == nullptr || _impl->tasks == 0; 67 | } 68 | -------------------------------------------------------------------------------- /source/park.cc: -------------------------------------------------------------------------------- 1 | 2 | // jobxx - C++ lightweight task library. 3 | // 4 | // This is free and unencumbered software released into the public domain. 5 | // 6 | // Anyone is free to copy, modify, publish, use, compile, sell, or 7 | // distribute this software, either in source code form or as a compiled 8 | // binary, for any purpose, commercial or non - commercial, and by any 9 | // means. 10 | // 11 | // In jurisdictions that recognize copyright laws, the author or authors 12 | // of this software dedicate any and all copyright interest in the 13 | // software to the public domain. We make this dedication for the benefit 14 | // of the public at large and to the detriment of our heirs and 15 | // successors. We intend this dedication to be an overt act of 16 | // relinquishment in perpetuity of all present and future rights to this 17 | // software under copyright law. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | // OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | // For more information, please refer to 28 | // 29 | // Authors: 30 | // Sean Middleditch 31 | 32 | #include "jobxx/park.h" 33 | #include 34 | #include 35 | 36 | struct jobxx::park::thread_state 37 | { 38 | thread_state() = default; 39 | 40 | thread_state(thread_state const&) = delete; 41 | thread_state& operator=(thread_state const&) = delete; 42 | 43 | // FIXME: we can make this more efficient on some platforms. 44 | // Linux, Win8+, etc. can sleep on the atomic's value/address (futexes). 45 | std::mutex _lock; 46 | std::condition_variable _cond; 47 | std::atomic _state = -2; 48 | 49 | }; 50 | 51 | jobxx::park_result jobxx::park::_park(park* first, predicate first_pred, park* second, predicate second_pred) 52 | { 53 | thread_local thread_state local_thread; 54 | thread_state& thread = local_thread; // can't capture thread_local variables in lambdas 55 | 56 | // we can't be parked again if we're already parked 57 | int expected = -1; 58 | if (!thread._state.compare_exchange_strong(expected, -1, std::memory_order_acquire)) 59 | { 60 | return park_result::failure; 61 | } 62 | 63 | // link into the park(s) that we want to be 64 | // awoken by. note that our parked state is not 65 | // guaranteed to still be true by the end of this 66 | // process, so _wait must deal with that. 67 | parked_node first_node; 68 | first_node._id = 0; 69 | first_node._thread = &thread; 70 | first->_link(first_node); 71 | 72 | if (first_pred && first_pred()) 73 | { 74 | first->_unlink(first_node); 75 | return park_result::first; 76 | } 77 | 78 | parked_node second_node; 79 | if (second != nullptr) 80 | { 81 | first_node._id = 1; 82 | second_node._thread = &thread; 83 | second->_link(second_node); 84 | 85 | if (second_pred && second_pred()) 86 | { 87 | // we may have been unparked by the prior condition if it triggered after its predicate but before now. 88 | if (thread._state.compare_exchange_strong(expected, first_node._id, std::memory_order_seq_cst)) 89 | { 90 | first->_unlink(first_node); 91 | second->_unlink(second_node); 92 | return park_result::second; 93 | } 94 | } 95 | } 96 | 97 | // we check the predicate after parking to avoid a race condition. 98 | // (1) the event may be triggered before parking. 99 | // (2) the event may be triggered after parking but before sleeping. 100 | // (3) the event may be triggered after sleeping. 101 | // the thread state and unpark logic will catch the second two. 102 | // the predicate is intended to catch the first. if the predicate 103 | // itself were checked before parking, then there would be a gap 104 | // of time before checking the predicate and parking in which the 105 | // event could be triggered and effectively lost. 106 | { 107 | std::unique_lock lock(thread._lock); 108 | thread._cond.wait(lock, [&thread](){ return thread._state.load() == -1; }); 109 | } 110 | 111 | // determine whom unlocked us, and reset our state back to its default. 112 | // note that the state will be either 0 or 1, which indicates which node 113 | // unparked this thread; it maps to the park_result values. 114 | int const old_state = thread._state.exchange(-2, std::memory_order::memory_order_seq_cst); 115 | 116 | // unlink from both parks, because we very possibly were only 117 | // unlinked by one of them, and we can't leave either with a 118 | // dangling reference to a node. 119 | first->_unlink(first_node); 120 | if (second != nullptr) 121 | { 122 | second->_unlink(second_node); 123 | } 124 | 125 | return static_cast(old_state); 126 | } 127 | 128 | bool jobxx::park::unpark_one() 129 | { 130 | std::lock_guard _(_lock); 131 | 132 | while (_parked._next != &_parked) 133 | { 134 | parked_node* const node = _parked._next; 135 | _parked._next = _parked._next->_next; 136 | _parked._next->_prev = &_parked; 137 | 138 | node->_prev = node->_next = node; 139 | 140 | // keep looping until we awaken a thread; 141 | // a thread may already be unparked by another 142 | // unpark operation even though it was still 143 | // in our queue, so we cannot assume that its 144 | // presence means we unlocked it. 145 | if (_unpark(*node->_thread)) 146 | { 147 | return true; 148 | } 149 | } 150 | 151 | return false; 152 | } 153 | 154 | void jobxx::park::unpark_all() 155 | { 156 | std::lock_guard _(_lock); 157 | 158 | // tell all currently-parked threads to awaken 159 | parked_node* node = _parked._next; 160 | while (node != &_parked) 161 | { 162 | parked_node* const next = node->_next; 163 | node->_prev = node->_next = node; 164 | _unpark(*node->_thread); 165 | node = next; 166 | } 167 | _parked._prev = _parked._next = &_parked; 168 | } 169 | 170 | bool jobxx::park::_unpark(thread_state& thread) 171 | { 172 | // signal a thread to awaken _if_ it's currently parked. 173 | int expected = -1; 174 | bool const awoken = thread._state.compare_exchange_strong(expected, 0, std::memory_order_release); 175 | if (awoken) 176 | { 177 | // the lock is held to avoid a race; condition_variable 178 | // conditions can _only_ be modified under the lock used 179 | // to wait to avoid a race condition. by holding the lock, 180 | // we ensure that the condition_variable cannot be actively 181 | // querying its condition at the time we signal it, and 182 | // that it either hasn't queried yet or that it's for-sure 183 | // blocking and waiting for the notify. 184 | std::lock_guard _(thread._lock); 185 | thread._cond.notify_one(); 186 | } 187 | return awoken; 188 | } 189 | 190 | void jobxx::park::_link(parked_node& node) 191 | { 192 | std::lock_guard _(_lock); 193 | 194 | node._next = &_parked; 195 | node._prev = _parked._prev; 196 | node._prev->_next = &node; 197 | _parked._prev = &node; 198 | } 199 | 200 | void jobxx::park::_unlink(parked_node& node) 201 | { 202 | std::lock_guard _(_lock); 203 | 204 | node._next->_prev = node._prev; 205 | node._prev->_next = node._next; 206 | } 207 | -------------------------------------------------------------------------------- /source/queue.cc: -------------------------------------------------------------------------------- 1 | 2 | // jobxx - C++ lightweight task library. 3 | // 4 | // This is free and unencumbered software released into the public domain. 5 | // 6 | // Anyone is free to copy, modify, publish, use, compile, sell, or 7 | // distribute this software, either in source code form or as a compiled 8 | // binary, for any purpose, commercial or non - commercial, and by any 9 | // means. 10 | // 11 | // In jurisdictions that recognize copyright laws, the author or authors 12 | // of this software dedicate any and all copyright interest in the 13 | // software to the public domain. We make this dedication for the benefit 14 | // of the public at large and to the detriment of our heirs and 15 | // successors. We intend this dedication to be an overt act of 16 | // relinquishment in perpetuity of all present and future rights to this 17 | // software under copyright law. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | // OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | // For more information, please refer to 28 | // 29 | // Authors: 30 | // Sean Middleditch 31 | 32 | #include "jobxx/queue.h" 33 | #include "jobxx/job.h" 34 | #include "jobxx/_detail/job_impl.h" 35 | #include "jobxx/_detail/queue_impl.h" 36 | #include "jobxx/_detail/task.h" 37 | 38 | jobxx::queue::queue() : _impl(new _detail::queue_impl) {} 39 | 40 | jobxx::queue::~queue() 41 | { 42 | close(); 43 | delete _impl; 44 | } 45 | 46 | void jobxx::queue::wait_job_actively(job const& awaited) 47 | { 48 | if (awaited.complete()) 49 | { 50 | return; 51 | } 52 | 53 | while (!awaited.complete()) 54 | { 55 | work_one(); 56 | 57 | _detail::task* item = nullptr; 58 | park_result const result = park::park_until( 59 | _impl->waiting, [&awaited]{ return awaited.complete(); }, 60 | awaited._impl->waiting, [this, &item]{ return (item = _impl->pull_task()) != nullptr; }); 61 | 62 | // if we were unparked by the task queue, that means that there is work 63 | // available. we will only have acquired the task already if it was ready 64 | // when the predicate was invoked, not if we actually slept. if we are 65 | // unparked by the task queue, that means a task is available and that no 66 | // other thread was unparked, so we either must process it or unpark another 67 | // thread in order to ensure that the work gets done in a timely manner. 68 | // FIXME: this addresses a race condition, but I'm really not happy with the 69 | // general design or interface here. 70 | if (result == park_result::second && item == nullptr) 71 | { 72 | item = _impl->pull_task(); 73 | } 74 | 75 | // we don't want to execute work inside the 76 | // parkable condition, but we have to act 77 | // on anything polled by it. 78 | if (item != nullptr) 79 | { 80 | _impl->execute(item); 81 | } 82 | } 83 | } 84 | 85 | bool jobxx::queue::work_one() 86 | { 87 | _detail::task* item = _impl->pull_task(); 88 | if (item != nullptr) 89 | { 90 | _impl->execute(item); 91 | return true; 92 | } 93 | else 94 | { 95 | return false; 96 | } 97 | } 98 | 99 | void jobxx::queue::work_all() 100 | { 101 | while (work_one()) 102 | { 103 | // keep looping while there's work 104 | } 105 | } 106 | 107 | void jobxx::queue::work_forever() 108 | { 109 | while (!_impl->closed.load(std::memory_order_relaxed)) 110 | { 111 | work_all(); 112 | 113 | _detail::task* item = nullptr; 114 | _impl->waiting.park_until([this, &item] 115 | { 116 | return _impl->closed.load(std::memory_order_relaxed) || (item = _impl->pull_task()) != nullptr; 117 | }); 118 | 119 | // we don't want to execute work inside the 120 | // parkable condition, but we have to act 121 | // on anything polled by it. 122 | if (item != nullptr) 123 | { 124 | _impl->execute(item); 125 | } 126 | } 127 | } 128 | 129 | void jobxx::queue::close() 130 | { 131 | // before closing _try_ to empty the task queue 132 | work_all(); 133 | 134 | // close the park, which ensures nobody is 135 | // blocked on this queue, by unparking all threads 136 | // after we mark the queue as closed (which 137 | // prevents reparking). 138 | _impl->closed.store(true); 139 | _impl->waiting.unpark_all(); 140 | 141 | // actually finish any work remaining, knowing 142 | // that no new work can be added to the queue 143 | // after closing the park. 144 | work_all(); 145 | } 146 | 147 | jobxx::_detail::job_impl* jobxx::queue::_create_job() 148 | { 149 | return new _detail::job_impl; 150 | } 151 | 152 | auto jobxx::queue::spawn_task(delegate&& work) -> spawn_result 153 | { 154 | return _impl->spawn_task(std::move(work), nullptr); 155 | } 156 | 157 | auto jobxx::_detail::queue_impl::spawn_task(delegate work, _detail::job_impl* parent) -> spawn_result 158 | { 159 | // task with no work is not allowed/useful 160 | if (!work) 161 | { 162 | return spawn_result::empty_function; 163 | } 164 | 165 | // we can't spawn tasks on closed queue 166 | if (closed.load(std::memory_order_acquire)) 167 | { 168 | return spawn_result::queue_full; 169 | } 170 | 171 | if (parent != nullptr) 172 | { 173 | // increment the number of pending tasks 174 | // and if this is the first task, add a 175 | // reference so the job isn't deleted 176 | // before the task completes. we only do 177 | // this count on the first/last task to 178 | // avoid excessive reference counting. 179 | if (0 == parent->tasks++) 180 | { 181 | ++parent->refs; 182 | } 183 | } 184 | 185 | _detail::task* item = new _detail::task{std::move(work), parent}; 186 | tasks.push_back(item); 187 | waiting.unpark_one(); 188 | 189 | return spawn_result::success; 190 | } 191 | 192 | jobxx::_detail::task* jobxx::_detail::queue_impl::pull_task() 193 | { 194 | jobxx::_detail::task* item = nullptr; 195 | tasks.pop_front(item); // on failure, item is left unmodified, e.g. nullptr 196 | return item; 197 | } 198 | 199 | void jobxx::_detail::queue_impl::execute(_detail::task* item) 200 | { 201 | if (item->work) 202 | { 203 | context ctx(*this, item->parent); 204 | item->work(ctx); 205 | } 206 | 207 | if (item->parent != nullptr) 208 | { 209 | // decrement the number of outstanding 210 | // tasks. if this is the last task that 211 | // was pending, also remove the reference 212 | // count we added when the first task was 213 | // added, since there are no longer any 214 | // tasks referencing the job. 215 | if (0 == --item->parent->tasks) 216 | { 217 | // awaken any parked threads awaiting the job 218 | item->parent->waiting.unpark_all(); 219 | 220 | if (0 == --item->parent->refs) 221 | { 222 | delete item->parent; 223 | } 224 | } 225 | } 226 | 227 | // the task is no longer needed 228 | delete item; 229 | } 230 | -------------------------------------------------------------------------------- /source/tests.cc: -------------------------------------------------------------------------------- 1 | 2 | // jobxx - C++ lightweight task library. 3 | // 4 | // This is free and unencumbered software released into the public domain. 5 | // 6 | // Anyone is free to copy, modify, publish, use, compile, sell, or 7 | // distribute this software, either in source code form or as a compiled 8 | // binary, for any purpose, commercial or non - commercial, and by any 9 | // means. 10 | // 11 | // In jurisdictions that recognize copyright laws, the author or authors 12 | // of this software dedicate any and all copyright interest in the 13 | // software to the public domain. We make this dedication for the benefit 14 | // of the public at large and to the detriment of our heirs and 15 | // successors. We intend this dedication to be an overt act of 16 | // relinquishment in perpetuity of all present and future rights to this 17 | // software under copyright law. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | // OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | // For more information, please refer to 28 | // 29 | // Authors: 30 | // Sean Middleditch 31 | 32 | #include "jobxx/queue.h" 33 | #include "jobxx/job.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | // test utilities and helpers 41 | namespace 42 | { 43 | 44 | class worker_pool 45 | { 46 | public: 47 | explicit worker_pool(int threads) 48 | { 49 | for (int i = 0; i < threads; ++i) 50 | { 51 | _threads.emplace_back([this](){ _queue.work_forever(); }); 52 | } 53 | } 54 | 55 | jobxx::queue& queue() { return _queue; } 56 | 57 | ~worker_pool() 58 | { 59 | _queue.close(); 60 | for (auto& thread : _threads) 61 | { 62 | thread.join(); 63 | } 64 | } 65 | 66 | private: 67 | jobxx::queue _queue; 68 | std::vector _threads; 69 | }; 70 | 71 | static bool execute(bool(*test)(), int times = 1) 72 | { 73 | for (int i = 0; i < times; ++i) 74 | { 75 | if (!test()) 76 | { 77 | return false; 78 | } 79 | } 80 | return true; 81 | } 82 | 83 | template 84 | static void spawn_n(ContextT& context, int count, FunctionT&& func) 85 | { 86 | for (int i = 0; i < count; ++i) 87 | { 88 | context.spawn_task(func); 89 | } 90 | } 91 | 92 | } 93 | 94 | // our tests 95 | namespace 96 | { 97 | 98 | // test the general queue/task/job system _without_ threads 99 | static bool basic_test() 100 | { 101 | jobxx::queue queue; 102 | 103 | int num = 0x1337c0de; 104 | int num2 = 0x600df00d; 105 | 106 | jobxx::job job = queue.create_job([&num, &num2](jobxx::context& ctx) 107 | { 108 | // spawn a task in the job (with no task context) 109 | ctx.spawn_task([&num](){ num = 0xdeadbeef; }); 110 | 111 | // spawn a task in the job (with task context) 112 | ctx.spawn_task([&num2](jobxx::context& ctx) 113 | { 114 | num2 = 0xdeadbeee; 115 | 116 | ctx.spawn_task([&num2](){ ++num2; }); 117 | }); 118 | }); 119 | queue.wait_job_actively(job); 120 | 121 | if (num != 0xdeadbeef || num2 != 0xdeadbeef) 122 | { 123 | return false; 124 | } 125 | 126 | return true; 127 | } 128 | 129 | // test background threads and the main thread actively working together 130 | static bool thread_test() 131 | { 132 | worker_pool pool(4); 133 | 134 | std::atomic counter = 0; 135 | for (int inc = 1; inc != 5; ++inc) 136 | { 137 | spawn_n(pool.queue(), 1000, [&counter, inc](){ counter += inc; }); 138 | } 139 | 140 | while (counter != (1000 + 2000 + 3000 + 4000)) 141 | { 142 | pool.queue().work_all(); 143 | } 144 | 145 | return true; 146 | } 147 | 148 | // test background threads working while the main thread does not execute tasks 149 | static bool inactive_wait_thread_test() 150 | { 151 | worker_pool pool(4); 152 | 153 | std::atomic counter = 0; 154 | constexpr int target = 16; 155 | spawn_n(pool.queue(), target, [&counter]() 156 | { 157 | std::this_thread::sleep_for(std::chrono::milliseconds(250)); 158 | counter += 1; 159 | }); 160 | 161 | // do _not_ wait actively here 162 | while (counter != target) 163 | { 164 | std::this_thread::sleep_for(std::chrono::seconds(1)); 165 | } 166 | 167 | return true; 168 | } 169 | 170 | static bool multi_queue_job_test() 171 | { 172 | worker_pool pool(2); 173 | 174 | std::atomic counter = 0; 175 | constexpr int target = 16; 176 | 177 | jobxx::job job = pool.queue().create_job([&counter, &pool, target](auto& ctx) 178 | { 179 | spawn_n(ctx, target, [&counter]() 180 | { 181 | std::this_thread::sleep_for(std::chrono::seconds(1)); 182 | counter += 1; 183 | }); 184 | }); 185 | 186 | // wait for the job on a queue that will never run work for it 187 | jobxx::queue queue; 188 | queue.wait_job_actively(job); 189 | 190 | return true; 191 | } 192 | 193 | } 194 | 195 | int main() 196 | { 197 | return !( 198 | execute(&basic_test, 10) && 199 | execute(&thread_test) && 200 | execute(&inactive_wait_thread_test) && 201 | execute(&multi_queue_job_test) 202 | ); 203 | } 204 | --------------------------------------------------------------------------------