├── .gitignore ├── LICENSE ├── README.md ├── examples ├── Makefile ├── parallel_executor.cpp ├── parallel_task_queue.cpp └── timer.h └── include ├── parallel_executor.h ├── parallel_task_queue.h ├── task_thread.h └── tuple_apply.h /.gitignore: -------------------------------------------------------------------------------- 1 | /Release* 2 | /Debug* 3 | /Profile* 4 | /Coverage* 5 | /build_* 6 | /dev 7 | /docs/* 8 | !/docs/doxyfile 9 | .project 10 | .cproject 11 | .settings 12 | .pydevproject 13 | *.json 14 | *.slo 15 | *.lo 16 | *.o 17 | *.so 18 | *.dylib 19 | *.lai 20 | *.la 21 | *.a 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AM parallel 2 | ========== 3 | 4 | Header-only parallel programming primitives for C++14. 5 | 6 | The repo also includes some examples. 7 | 8 | 9 | ## Classes 10 | 11 | #### task\_thread 12 | - pausable thread 13 | - basis for parallel\_task\_queue 14 | 15 | #### parallel\_executor 16 | - runs a batch of tasks in parallel 17 | - works on task iterators, doesn't own tasks 18 | - forwards call args to all tasks 19 | - blocks calling thread until all tasks are finished 20 | 21 | #### parallel\_task\_queue 22 | - runs tasks in parallel 23 | - enqueue & run can interleave 24 | - owns tasks 25 | 26 | 27 | ## Requirements 28 | - requires (mostly) C++14 conforming compiler 29 | - tested with g++ {5.3, 7.2} 30 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | 3 | INCLUDE = ../include 4 | FILTER = * 5 | SOURCES = $(wildcard $(FILTER).cpp) 6 | EXECS = $(notdir $(SOURCES:.cpp=)) 7 | CXX = g++ 8 | FLAGS = -I $(INCLUDE) -std=c++14 -O3 -Wall -pthread 9 | 10 | INCLUDES = \ 11 | $(INCLUDE)/parallel_executor.h \ 12 | $(INCLUDE)/parallel_task_queue.h \ 13 | $(INCLUDE)/task_thread.h \ 14 | timer.h 15 | 16 | .PHONY: all clean 17 | 18 | all: $(EXECS) 19 | 20 | % : %.cpp $(INCLUDES) 21 | $(CXX) $(FLAGS) -o $@ $< 22 | 23 | clean: 24 | find -type f -not -name "*.cpp" -not -name "*.h" -not -name "Makefile" -not -name "runall" | xargs rm 25 | -------------------------------------------------------------------------------- /examples/parallel_executor.cpp: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * AM numeric facilities 4 | * 5 | * released under MIT license 6 | * 7 | * 2015-2018 André Müller 8 | * 9 | *****************************************************************************/ 10 | 11 | #include 12 | 13 | #include "../include/parallel_executor.h" 14 | #include "timer.h" 15 | 16 | 17 | 18 | //------------------------------------------------------------------- 19 | struct test_task 20 | { 21 | test_task(int taskid): id(taskid) {} 22 | 23 | void operator () (int ms = 8) { 24 | std::this_thread::sleep_for(std::chrono::milliseconds(ms)); 25 | } 26 | 27 | int id; 28 | }; 29 | 30 | 31 | 32 | //------------------------------------------------------------------- 33 | int main() 34 | { 35 | using namespace am; 36 | 37 | std::cout << "executing parallel tasks" << std::endl; 38 | 39 | timer time; 40 | std::vector tasks1; 41 | parallel_executor::iterator,int> runner1(8); 42 | 43 | for(int i = 0; i < 1000; ++i) { 44 | tasks1.emplace_back(i); 45 | } 46 | 47 | time.start(); 48 | runner1(tasks1, 6); 49 | time.stop(); 50 | 51 | time.start(); 52 | runner1(tasks1, 10); 53 | time.stop(); 54 | 55 | 56 | std::vector> tasks2; 57 | parallel_executor>::iterator> runner2(8); 58 | 59 | for(int i = 0; i < 126; ++i) { 60 | tasks2.push_back([i](){ 61 | std::this_thread::sleep_for(std::chrono::milliseconds(i)); 62 | }); 63 | } 64 | 65 | time.start(); 66 | runner2(begin(tasks2), end(tasks2)); 67 | time.stop(); 68 | 69 | tasks2.clear(); 70 | for(int i = 0; i < 89; ++i) { 71 | tasks2.push_back([i](){ 72 | std::this_thread::sleep_for(std::chrono::milliseconds(2*i)); 73 | }); 74 | } 75 | 76 | time.start(); 77 | runner2(begin(tasks2), end(tasks2)); 78 | time.stop(); 79 | 80 | 81 | std::cout << "finished tasks in " << time.milliseconds() << " ms" << std::endl; 82 | } 83 | 84 | 85 | -------------------------------------------------------------------------------- /examples/parallel_task_queue.cpp: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * AM numeric facilities 4 | * 5 | * released under MIT license 6 | * 7 | * 2015-2018 André Müller 8 | * 9 | *****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "../include/parallel_task_queue.h" 17 | #include "timer.h" 18 | 19 | 20 | //------------------------------------------------------------------- 21 | double parallel_sum(const std::vector& v) { 22 | constexpr int n = 64; 23 | 24 | am::parallel_queue q; 25 | 26 | auto psums = std::vector(n+1, 0.0); 27 | const auto chunk = v.size() / n; 28 | for(int i = 0; i < n; ++i) { 29 | q.enqueue([&,i]{ 30 | psums[i] = std::accumulate(begin(v) + (i*chunk), 31 | begin(v) + ((i+1)*chunk), 0.0); 32 | }); 33 | } 34 | if(v.size() % n > 0) { 35 | q.enqueue([&]{ 36 | psums.back() = std::accumulate(begin(v) + (chunk*n), end(v), 0.0); 37 | }); 38 | } 39 | 40 | q.wait(); 41 | return std::accumulate(begin(psums), end(psums), 0.0); 42 | } 43 | 44 | 45 | 46 | //------------------------------------------------------------------- 47 | int main() 48 | { 49 | constexpr size_t n = 1 << 26; 50 | std::cout << "creating " << n << " values... " << std::flush; 51 | 52 | am::timer time; 53 | time.start(); 54 | 55 | auto nums = std::vector(n); 56 | std::iota(begin(nums), end(nums), 0); 57 | 58 | time.stop(); 59 | std::cout << time.milliseconds() << " ms" << std::endl; 60 | 61 | time.restart(); 62 | auto sersum = std::accumulate(nums.begin(), nums.end(), 0.0); 63 | time.stop(); 64 | auto sertime = time.milliseconds(); 65 | 66 | time.restart(); 67 | auto parsum = parallel_sum(nums); 68 | time.stop(); 69 | auto partime = time.milliseconds(); 70 | 71 | time.stop(); 72 | std::cout << "serial result: " << sersum << '\n' 73 | << "parallel result: " << parsum << '\n' 74 | << "serial time: " << sertime << " ms\n" 75 | << "parallel time: " << partime << " ms\n" 76 | << "speedup: " << (sertime/double(partime)) 77 | << std::endl; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /examples/timer.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * AM utilities 4 | * 5 | * released under MIT license 6 | * 7 | * 2008-2018 André Müller 8 | * 9 | *****************************************************************************/ 10 | 11 | #ifndef AM_TIMER_H_ 12 | #define AM_TIMER_H_ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | namespace am { 21 | 22 | 23 | /***************************************************************************** 24 | * 25 | * @brief simple std::chrono based timer class 26 | * 27 | *****************************************************************************/ 28 | class timer 29 | { 30 | using basic_duration_t = std::chrono::microseconds; 31 | 32 | public: 33 | //--------------------------------------------------------------- 34 | void 35 | start() noexcept { 36 | if(!running_) { 37 | running_ = true; 38 | start_ = std::chrono::high_resolution_clock::now(); 39 | } 40 | } 41 | //----------------------------------------------------- 42 | void 43 | reset() noexcept { 44 | total_ = basic_duration_t(0); 45 | running_ = false; 46 | } 47 | //----------------------------------------------------- 48 | void 49 | restart() noexcept { 50 | reset(); 51 | start(); 52 | } 53 | 54 | //----------------------------------------------------- 55 | void 56 | stop() noexcept { 57 | if(running_) { 58 | stop_ = std::chrono::high_resolution_clock::now(); 59 | total_ += std::chrono::duration_cast(stop_-start_); 60 | running_ = false; 61 | } 62 | } 63 | 64 | 65 | //--------------------------------------------------------------- 66 | bool 67 | running() const noexcept { 68 | return running_; 69 | } 70 | 71 | 72 | //--------------------------------------------------------------- 73 | template 74 | Unit 75 | elapsed() const noexcept { 76 | return std::chrono::duration_cast(current()); 77 | } 78 | 79 | 80 | //----------------------------------------------------- 81 | auto 82 | microseconds() const noexcept { 83 | return elapsed().count(); 84 | } 85 | 86 | auto 87 | milliseconds() const noexcept { 88 | return elapsed().count(); 89 | } 90 | 91 | auto 92 | full_seconds() const noexcept { 93 | return elapsed().count(); 94 | } 95 | 96 | auto 97 | full_minutes() const noexcept { 98 | return elapsed().count(); 99 | } 100 | 101 | auto 102 | full_hours() const noexcept { 103 | return elapsed().count(); 104 | } 105 | 106 | 107 | //----------------------------------------------------- 108 | double 109 | seconds() const noexcept { 110 | return (double(milliseconds()) / 1000.0); 111 | } 112 | 113 | double 114 | minutes() const noexcept { 115 | return (double(milliseconds()) / 60000.0); 116 | } 117 | 118 | double 119 | hours() const noexcept { 120 | return (double(milliseconds()) / 3600000.0); 121 | } 122 | 123 | 124 | //----------------------------------------------------- 125 | std::string 126 | hh_mm_ss() const noexcept 127 | { 128 | std::ostringstream ss; 129 | int h = static_cast(full_hours()); 130 | int m = static_cast(full_minutes()); 131 | int s = static_cast(full_seconds()); 132 | if(h < 10) { ss << "0"; } ss << h << ":"; 133 | if(m < 10) { ss << "0"; } ss << m << ":"; 134 | if(s < 10) { ss << "0"; } ss << s; 135 | return ss.str(); 136 | } 137 | 138 | 139 | private: 140 | //----------------------------------------------------- 141 | basic_duration_t 142 | current() const noexcept { 143 | if(!running_) return total_; 144 | 145 | return ( 146 | total_ + (std::chrono::duration_cast( 147 | std::chrono::high_resolution_clock::now() - start_)) ); 148 | } 149 | 150 | //--------------------------------------------------------------- 151 | std::chrono::high_resolution_clock::time_point start_, stop_; 152 | basic_duration_t total_ = basic_duration_t(0); 153 | bool running_ = false; 154 | }; 155 | 156 | 157 | 158 | } // namespace am 159 | 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /include/parallel_executor.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * AM utilities 4 | * 5 | * released under MIT license 6 | * 7 | * 2008-2018 André Müller 8 | * 9 | *****************************************************************************/ 10 | 11 | #ifndef AM_PARALLEL_ECXECUTOR_H_ 12 | #define AM_PARALLEL_ECXECUTOR_H_ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "tuple_apply.h" //because we don't have std::apply yet :-( 25 | 26 | 27 | namespace am { 28 | 29 | 30 | /***************************************************************************** 31 | * 32 | * @brief runs a sequence of tasks in parallel 33 | * 34 | * A task in this context is anything that is callable and that 35 | * either returns void or the return value can be discarded. 36 | * 37 | * @details Internally a number of threads is always kept alive and fired up 38 | * as needed. 39 | * 40 | * @tparam TaskIterator: InputIterator over tasks 41 | * the task type itself must be callable, so either be a function 42 | * pointer or a type that implements an operator() that can be 43 | * invoked with CallArgs... e.g. std::function 44 | * 45 | * @tparam CallArgs...: 46 | * 47 | *****************************************************************************/ 48 | template 49 | class parallel_executor 50 | { 51 | //used to store call arguments 52 | using args_type = std::tuple; 53 | 54 | enum class status { idle = 0, busy = 1, terminate = 2 }; 55 | 56 | 57 | public: 58 | //----------------------------------------------------- 59 | using iterator = TaskIterator; 60 | using task_type = std::decay_t())>; 61 | 62 | 63 | //--------------------------------------------------------------- 64 | explicit 65 | parallel_executor(std::size_t concurrency = std::thread::hardware_concurrency()): 66 | mutables_{}, 67 | isDone_{}, wakeUp_{}, 68 | last_{}, first_{last_}, numUnfinished_{0}, 69 | status_{status::idle}, 70 | args_{}, workers_{} 71 | { 72 | make_workers(concurrency); 73 | } 74 | 75 | //----------------------------------------------------- 76 | parallel_executor(const parallel_executor&) = delete; 77 | parallel_executor(parallel_executor&& ) = delete; 78 | 79 | 80 | //--------------------------------------------------------------- 81 | parallel_executor& operator = (const parallel_executor&) = delete; 82 | parallel_executor& operator = (parallel_executor&&) = delete; 83 | 84 | 85 | //----------------------------------------------------- 86 | ~parallel_executor() { 87 | abort(); 88 | } 89 | 90 | 91 | //--------------------------------------------------------------- 92 | std::size_t 93 | concurrency() const noexcept { 94 | return workers_.size(); 95 | } 96 | 97 | 98 | //--------------------------------------------------------------- 99 | /** 100 | * @brief runs a range of tasks and 101 | * blocks execution of calling thread until all tasks are complete 102 | */ 103 | template> 105 | void operator () (Range& range, Args&&... args) 106 | { 107 | using std::begin; 108 | using std::end; 109 | 110 | operator()(iterator(begin(range)), iterator(end(range)), 111 | std::forward(args)...); 112 | } 113 | 114 | //----------------------------------------------------- 115 | /** 116 | * @brief runs a range of tasks and 117 | * blocks execution of calling thread until all tasks are complete 118 | */ 119 | template> 121 | void 122 | operator () (iterator first, iterator last, Args&&... args) 123 | { 124 | if(running() || (first == last)) return; 125 | 126 | std::unique_lock lock(mutables_); 127 | 128 | first_ = first; 129 | last_ = last; 130 | using std::distance; 131 | numUnfinished_ = distance(first,last); 132 | 133 | //store call arguments 134 | args_ = args_type{std::forward(args)...}; 135 | 136 | //fire up all worker threads 137 | status_.store(status::busy); 138 | wakeUp_.notify_all(); 139 | 140 | //block execution of local thread until all tasks are complete 141 | while(running()) isDone_.wait(lock); 142 | } 143 | 144 | 145 | //--------------------------------------------------------------- 146 | void 147 | stop() { 148 | if(status_ != status::busy) return; 149 | 150 | std::lock_guard lock(mutables_); 151 | 152 | //mark all tasks as finished 153 | last_ = first_; 154 | 155 | //signal all threads to suspend after finishing their current task 156 | status_.store(status::idle); 157 | wakeUp_.notify_all(); 158 | } 159 | 160 | 161 | //--------------------------------------------------------------- 162 | bool 163 | running() const noexcept { 164 | return status_.load() == status::busy; 165 | } 166 | 167 | 168 | private: 169 | 170 | //--------------------------------------------------------------- 171 | void 172 | make_workers(std::size_t n) 173 | { 174 | if(n < 1) n = 1; 175 | 176 | workers_.reserve(n); 177 | 178 | //construct worker threads and suspend execution 179 | for(std::size_t i = 0; i < n; ++i) { 180 | workers_.emplace_back(std::thread( [&] { 181 | while(!abort_requested()) { 182 | wait_until_needed(); 183 | if(!abort_requested()) 184 | try_run_next_task(); 185 | } 186 | })); 187 | } 188 | } 189 | 190 | 191 | //--------------------------------------------------------------- 192 | bool 193 | abort_requested() const noexcept { 194 | return (status_.load() == status::terminate); 195 | } 196 | 197 | //----------------------------------------------------- 198 | /// @brief this will render the object non-runnable 199 | void 200 | abort() { 201 | using std::next; 202 | 203 | { 204 | std::lock_guard lock(mutables_); 205 | 206 | //mark all tasks as finished 207 | last_ = next(first_); 208 | numUnfinished_ = 0; 209 | 210 | //signal all threads to terminate immediatly 211 | status_.store(status::terminate); 212 | wakeUp_.notify_all(); 213 | } 214 | 215 | //wait for all threads to be finished 216 | for(auto& w : workers_) w.join(); 217 | } 218 | 219 | 220 | //--------------------------------------------------------------- 221 | void 222 | wait_until_needed() { 223 | std::unique_lock lock(mutables_); 224 | 225 | //block execution until woken up 226 | //(while loop protects against spurious wake-ups) 227 | while(status_.load() == status::idle || (first_ == last_)) { 228 | wakeUp_.wait(lock); 229 | } 230 | } 231 | 232 | //--------------------------------------------------------------- 233 | /// @brief execute next unfinished task (or signal finished) 234 | void 235 | try_run_next_task() { 236 | iterator current; 237 | { 238 | std::lock_guard lock(mutables_); 239 | 240 | if(first_ == last_) return; 241 | 242 | current = first_; 243 | ++first_; 244 | } 245 | 246 | //run task: current->operator()(args_...) 247 | apply(*current, args_); 248 | 249 | --numUnfinished_; 250 | //finished? => unblock everything 251 | if(numUnfinished_.load() < 1) { 252 | status_.store(status::idle); 253 | isDone_.notify_one(); 254 | } 255 | } 256 | 257 | 258 | //--------------------------------------------------------------- 259 | mutable std::mutex mutables_; 260 | std::condition_variable isDone_; 261 | std::condition_variable wakeUp_; 262 | iterator last_; 263 | iterator first_; 264 | std::atomic numUnfinished_; 265 | std::atomic status_; 266 | args_type args_; 267 | std::vector workers_; 268 | }; 269 | 270 | 271 | } // namespace am 272 | 273 | 274 | #endif 275 | -------------------------------------------------------------------------------- /include/parallel_task_queue.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * AM utilities 4 | * 5 | * released under MIT license 6 | * 7 | * 2008-2018 André Müller 8 | * 9 | *****************************************************************************/ 10 | 11 | #ifndef AMLIB_PARALLEL_TASK_QUEUE_H_ 12 | #define AMLIB_PARALLEL_TASK_QUEUE_H_ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "task_thread.h" 23 | 24 | 25 | namespace am { 26 | 27 | 28 | /***************************************************************************** 29 | * 30 | * @brief simple queue that holds tasks and executes them in parallel 31 | * 32 | * @tparam Task : assumed to be cheap to copy or move 33 | * (e.g. std::function, std::reference_wrapper, ...) 34 | * Task::operator()() must be defined 35 | * 36 | *****************************************************************************/ 37 | template 38 | class parallel_task_queue 39 | { 40 | public: 41 | //--------------------------------------------------------------- 42 | using task_type = Task; 43 | 44 | 45 | private: 46 | //--------------------------------------------------------------- 47 | class task_executor { 48 | public: 49 | explicit 50 | task_executor(parallel_task_queue* callback = nullptr, 51 | task_type&& task = task_type{}) noexcept 52 | : 53 | queue_{callback}, 54 | task_{std::move(task)} 55 | {} 56 | 57 | void operator () () { 58 | task_(); 59 | 60 | std::unique_lock lock{queue_->busyMtx_}; 61 | --queue_->running_; 62 | (*queue_).notify_task_complete(); 63 | } 64 | 65 | private: 66 | parallel_task_queue* queue_; 67 | task_type task_; 68 | }; 69 | 70 | friend class task_executor; 71 | 72 | 73 | public: 74 | //--------------------------------------------------------------- 75 | explicit 76 | parallel_task_queue(unsigned int concurrency = 77 | std::thread::hardware_concurrency()) 78 | : 79 | waitMtx_{}, enqueueMtx_{}, 80 | active_{true}, hasWaiting_{false}, 81 | running_{0}, 82 | waiting_{}, 83 | workers_(std::min(concurrency, std::thread::hardware_concurrency())), 84 | isDone_{}, 85 | busyMtx_{}, 86 | isBusy_{}, 87 | isWaitingForTask_{}, 88 | scheduler_{ [&] { schedule(); }} 89 | {} 90 | 91 | //----------------------------------------------------- 92 | parallel_task_queue(const parallel_task_queue&) = delete; 93 | parallel_task_queue(parallel_task_queue&&) = delete; 94 | 95 | 96 | //----------------------------------------------------- 97 | ~parallel_task_queue() 98 | { 99 | clear(); 100 | //make sure that scheduler wakes up and terminates 101 | std::unique_lock lock{enqueueMtx_}; 102 | active_.store(false); 103 | isWaitingForTask_.notify_one(); 104 | lock.unlock(); 105 | //wait for scheduler to terminate 106 | scheduler_.join(); 107 | } 108 | 109 | 110 | //--------------------------------------------------------------- 111 | parallel_task_queue& operator = (const parallel_task_queue&) = delete; 112 | parallel_task_queue& operator = (parallel_task_queue&&) = delete; 113 | 114 | 115 | //--------------------------------------------------------------- 116 | void 117 | enqueue(const task_type& t) 118 | { 119 | std::lock_guard lock(enqueueMtx_); 120 | waiting_.push_back(t); 121 | hasWaiting_.store(true); 122 | isWaitingForTask_.notify_one(); 123 | } 124 | //----------------------------------------------------- 125 | void 126 | enqueue(task_type&& t) 127 | { 128 | std::lock_guard lock(enqueueMtx_); 129 | waiting_.push_back(std::move(t)); 130 | hasWaiting_.store(true); 131 | isWaitingForTask_.notify_one(); 132 | } 133 | //----------------------------------------------------- 134 | template 135 | void 136 | enqueue(InputIterator first, InputIterator last) 137 | { 138 | std::lock_guard lock(enqueueMtx_); 139 | waiting_.insert(begin(waiting_), first, last); 140 | hasWaiting_.store(true); 141 | isWaitingForTask_.notify_one(); 142 | } 143 | //----------------------------------------------------- 144 | void 145 | enqueue(std::initializer_list il) 146 | { 147 | std::lock_guard lock(enqueueMtx_); 148 | waiting_.insert(waiting_.end(), il); 149 | hasWaiting_.store(true); 150 | isWaitingForTask_.notify_one(); 151 | } 152 | //----------------------------------------------------- 153 | template 154 | void 155 | enqueue_emplace(Args&&... args) 156 | { 157 | std::lock_guard lock(enqueueMtx_); 158 | waiting_.emplace_back(std::forward(args)...); 159 | hasWaiting_.store(true); 160 | isWaitingForTask_.notify_one(); 161 | } 162 | 163 | 164 | /**************************************************************** 165 | * @brief removes first waiting task that compares equal to 't' 166 | */ 167 | bool 168 | try_remove(const task_type& t) 169 | { 170 | std::lock_guard lock(enqueueMtx_); 171 | auto it = std::find(waiting_.begin(), waiting_.end(), t); 172 | if(it != waiting_.end()) { 173 | waiting_.erase(it); 174 | if(waiting_.empty()) hasWaiting_.store(false); 175 | return true; 176 | } 177 | return false; 178 | } 179 | 180 | 181 | //--------------------------------------------------------------- 182 | void 183 | clear() { 184 | std::lock_guard lock(enqueueMtx_); 185 | waiting_.clear(); 186 | hasWaiting_.store(false); 187 | } 188 | 189 | 190 | //--------------------------------------------------------------- 191 | unsigned int 192 | concurrency() const noexcept { 193 | return workers_.size(); 194 | } 195 | 196 | //--------------------------------------------------------------- 197 | bool 198 | empty() const noexcept { 199 | return !hasWaiting_.load(); 200 | } 201 | 202 | 203 | //----------------------------------------------------- 204 | /// @return number of waiting tasks 205 | std::size_t 206 | waiting() const noexcept { 207 | std::lock_guard lock{enqueueMtx_}; 208 | return waiting_.size(); 209 | } 210 | std::size_t 211 | unsafe_waiting() const noexcept { 212 | return waiting_.size(); 213 | } 214 | 215 | //----------------------------------------------------- 216 | /// @return number of running tasks 217 | std::size_t 218 | running() const noexcept { 219 | return running_.load(); 220 | } 221 | 222 | //----------------------------------------------------- 223 | /// @return true, if all threads are working on a task 224 | bool 225 | busy() const noexcept { 226 | return running_.load() >= int(workers_.size()); 227 | } 228 | 229 | //----------------------------------------------------- 230 | bool 231 | complete() const noexcept { 232 | std::lock_guard lock{enqueueMtx_}; 233 | return empty() && (running() < 1); 234 | } 235 | 236 | 237 | //--------------------------------------------------------------- 238 | /** 239 | * @brief block execution of calling thread until all tasks are completed which are currently pending or running 240 | */ 241 | void wait() 242 | { 243 | /* 244 | Submit a barrier to each worker thread. Calling thread blocks until each worker thread reached the barrier 245 | */ 246 | std::unique_lock enqueuelock{enqueueMtx_}; 247 | 248 | auto barrierCount = std::make_shared>(int(concurrency())); 249 | 250 | std::condition_variable cv; 251 | 252 | auto barrierFunc = [&, barrierCount](){ 253 | std::unique_lock lock{waitMtx_}; 254 | --(*barrierCount); 255 | 256 | if((*barrierCount) == 0){ 257 | cv.notify_all(); 258 | }else{ 259 | cv.wait(lock, [&](){return (*barrierCount) == 0;}); 260 | } 261 | }; 262 | 263 | for(int i = 0; i < int(concurrency()); i++){ 264 | enqueue(barrierFunc); 265 | } 266 | 267 | enqueuelock.unlock(); 268 | 269 | std::unique_lock lock{waitMtx_}; 270 | if((*barrierCount) != 0){ 271 | cv.wait(lock, [&](){return (*barrierCount) == 0;}); 272 | } 273 | } 274 | 275 | 276 | private: 277 | //----------------------------------------------------- 278 | void notify_task_complete() { 279 | isBusy_.notify_one(); 280 | } 281 | 282 | 283 | //--------------------------------------------------------------- 284 | void try_assign_tasks() 285 | { 286 | for(auto& worker : workers_) { 287 | if(worker.available()) { 288 | std::lock_guard lock{enqueueMtx_}; 289 | if(waiting_.empty()) { 290 | hasWaiting_.store(false); 291 | return; 292 | } 293 | if(worker(this, std::move(waiting_.front()))) { 294 | ++running_; 295 | waiting_.pop_front(); 296 | if(waiting_.empty()) { 297 | hasWaiting_.store(false); 298 | return; 299 | } 300 | else if(busy()) { 301 | return; 302 | } 303 | } 304 | } 305 | } 306 | } 307 | 308 | //--------------------------------------------------------------- 309 | /// @brief this will run in a separate, dedicated thread 310 | void schedule() 311 | { 312 | while(active_.load()) { 313 | if(busy()) { 314 | std::unique_lock lock{busyMtx_}; 315 | if(busy()){ 316 | isBusy_.wait(lock, [this]{ return !busy(); }); 317 | } 318 | } 319 | else if(!empty()) { 320 | try_assign_tasks(); 321 | } 322 | else{ 323 | std::unique_lock lock{enqueueMtx_}; 324 | isWaitingForTask_.wait(lock, [this](){return !active_.load() || !empty();}); 325 | } 326 | } 327 | } 328 | 329 | 330 | //--------------------------------------------------------------- 331 | mutable std::mutex waitMtx_; 332 | mutable std::recursive_mutex enqueueMtx_; 333 | std::atomic_bool active_; 334 | std::atomic_bool hasWaiting_; 335 | std::atomic_int running_; 336 | std::deque waiting_; 337 | std::vector> workers_; 338 | std::condition_variable isDone_; 339 | std::mutex busyMtx_; 340 | std::condition_variable isBusy_; 341 | std::condition_variable_any isWaitingForTask_; 342 | std::thread scheduler_; 343 | }; 344 | 345 | 346 | 347 | //------------------------------------------------------------------- 348 | /// @brief convenience alias 349 | using parallel_queue = parallel_task_queue>; 350 | 351 | 352 | } // namespace am 353 | 354 | 355 | #endif 356 | -------------------------------------------------------------------------------- /include/task_thread.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * AM utilities 4 | * 5 | * released under MIT license 6 | * 7 | * 2008-2018 André Müller 8 | * 9 | *****************************************************************************/ 10 | 11 | #ifndef AM_PARALLEL_TASK_THREAD_H_ 12 | #define AM_PARALLEL_TASK_THREAD_H_ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | namespace am { 21 | 22 | 23 | /***************************************************************************** 24 | * 25 | * @brief thread that executes a single task and waits after completion 26 | * until execution is demanded again 27 | * 28 | *****************************************************************************/ 29 | template 30 | class task_thread 31 | { 32 | enum class status {busy = 0, idle = 1, terminate = 2 }; 33 | 34 | public: 35 | //--------------------------------------------------------------- 36 | using task_type = Task; 37 | 38 | 39 | //--------------------------------------------------------------- 40 | explicit 41 | task_thread(): 42 | mutables_{}, wakeup_{}, isIdle_{}, 43 | status_{status::idle}, 44 | task_{}, 45 | thread_{ [&] { work(); }} 46 | {} 47 | 48 | //--------------------------------------------------------------- 49 | template< 50 | class Arg, class... Args, class = 51 | typename std::enable_if::type,task_thread>::value,Arg>::type 52 | > 53 | explicit 54 | task_thread(Arg&& arg, Args&&... args): 55 | mutables_{}, wakeup_{}, isIdle_{}, 56 | status_{status::idle}, 57 | task_{std::forward(arg), std::forward(args)...}, 58 | thread_{ [&] { work(); }} 59 | {} 60 | 61 | 62 | //--------------------------------------------------------------- 63 | ~task_thread() { 64 | //signal thread to terminate 65 | status_.store(status::terminate); 66 | wakeup_.notify_all(); 67 | isIdle_.notify_all(); 68 | 69 | wait(); 70 | 71 | thread_.join(); 72 | } 73 | 74 | 75 | //--------------------------------------------------------------- 76 | task_thread(const task_thread& source): 77 | mutables_{}, wakeup_{}, isIdle_{}, 78 | status_{status::idle}, 79 | task_{source.task_}, 80 | thread_{ [&] { work(); }} 81 | {} 82 | 83 | //--------------------------------------------------------------- 84 | task_thread(task_thread&& source) 85 | noexcept(noexcept(task_type(std::declval()))) 86 | : 87 | mutables_{}, wakeup_{}, isIdle_{}, 88 | status_{status::idle}, 89 | task_{std::move(source.task_)}, 90 | thread_{ [&] { work(); }} 91 | {} 92 | 93 | 94 | //--------------------------------------------------------------- 95 | task_thread& operator = (const task_thread& source) 96 | { 97 | wait(); 98 | 99 | std::lock_guard lock(mutables_); 100 | task_ = source.task_; 101 | 102 | return *this; 103 | } 104 | 105 | //--------------------------------------------------------------- 106 | task_thread& operator = (task_thread&& source) 107 | noexcept(noexcept(std::declval() = source.task_)) 108 | { 109 | wait(); 110 | 111 | std::lock_guard lock(mutables_); 112 | task_ = std::move(source.task_); 113 | 114 | return *this; 115 | } 116 | 117 | 118 | //--------------------------------------------------------------- 119 | bool available() const noexcept { 120 | return bool(status_.load()); 121 | } 122 | 123 | 124 | //--------------------------------------------------------------- 125 | /** 126 | * @brief block execution of calling thread until task is finished 127 | */ 128 | void wait() 129 | { 130 | std::unique_lock lock(mutables_); 131 | 132 | //(while loop protects against spurious wake-ups) 133 | while(status_.load() == status::busy) { 134 | isIdle_.wait(lock); 135 | } 136 | } 137 | 138 | 139 | //--------------------------------------------------------------- 140 | /** 141 | * @brief non-thread-safe access to current task object 142 | */ 143 | const task_type& 144 | unsafe_task() const & noexcept { 145 | return task_; 146 | } 147 | /** 148 | * @brief non-thread-safe access to current task object 149 | */ 150 | task_type& 151 | unsafe_task() & noexcept { 152 | return task_; 153 | } 154 | /** 155 | * @brief extract current task object 156 | */ 157 | task_type&& 158 | extract_task() && noexcept { 159 | return std::move(task_); 160 | } 161 | 162 | 163 | //--------------------------------------------------------------- 164 | bool operator () () 165 | { 166 | if(status_.load() != status::idle) return false; 167 | std::lock_guard lock{mutables_}; 168 | status_.store(status::busy); 169 | 170 | wakeup_.notify_all(); 171 | return true; 172 | } 173 | 174 | //--------------------------------------------------------------- 175 | bool operator () (const task_type& task) 176 | { 177 | if(status_.load() != status::idle) return false; 178 | std::lock_guard lock{mutables_}; 179 | status_.store(status::busy); 180 | 181 | task_ = task; 182 | 183 | wakeup_.notify_all(); 184 | return true; 185 | } 186 | 187 | //--------------------------------------------------------------- 188 | bool operator () (task_type&& task) 189 | { 190 | if(status_.load() != status::idle) return false; 191 | std::lock_guard lock{mutables_}; 192 | status_.store(status::busy); 193 | 194 | task_ = std::move(task); 195 | 196 | wakeup_.notify_all(); 197 | return true; 198 | } 199 | 200 | //--------------------------------------------------------------- 201 | template 202 | bool operator () (Arg&& arg, Args&&... args) 203 | { 204 | if(status_.load() != status::idle) return false; 205 | std::lock_guard lock{mutables_}; 206 | status_.store(status::busy); 207 | 208 | task_ = task_type{std::forward(arg), 209 | std::forward(args)...}; 210 | 211 | wakeup_.notify_all(); 212 | return true; 213 | } 214 | 215 | 216 | private: 217 | 218 | //--------------------------------------------------------------- 219 | void work() 220 | { 221 | while(!abort_requested()) { 222 | wait_until_needed(); 223 | 224 | if(!abort_requested()) { 225 | task_(); 226 | status_.store(status::idle); 227 | isIdle_.notify_all(); 228 | } 229 | } 230 | isIdle_.notify_all(); 231 | } 232 | 233 | 234 | //--------------------------------------------------------------- 235 | bool abort_requested() const noexcept { 236 | return (status_.load() == status::terminate); 237 | } 238 | 239 | 240 | //--------------------------------------------------------------- 241 | void wait_until_needed() { 242 | std::unique_lock lock{mutables_}; 243 | 244 | //block execution of calling thread until woken up 245 | //(while loop protects against spurious wake-ups) 246 | while(status_.load() == status::idle) { 247 | wakeup_.wait(lock); 248 | } 249 | } 250 | 251 | 252 | //--------------------------------------------------------------- 253 | mutable std::mutex mutables_; 254 | std::condition_variable wakeup_; 255 | std::condition_variable isIdle_; 256 | std::atomic status_; 257 | task_type task_; 258 | std::thread thread_; 259 | }; 260 | 261 | 262 | } // namespace am 263 | 264 | 265 | #endif 266 | -------------------------------------------------------------------------------- /include/tuple_apply.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * AM utilities 4 | * 5 | * released under MIT license 6 | * 7 | * 2008-2018 André Müller 8 | * 9 | *****************************************************************************/ 10 | 11 | #ifndef AM_GENERIC_TUPLE_APPLY_H_ 12 | #define AM_GENERIC_TUPLE_APPLY_H_ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | namespace am { 20 | 21 | namespace detail { 22 | 23 | /***************************************************************************** 24 | * 25 | * @brief helper function exploding the tuple arguments into a function call 26 | * 27 | *****************************************************************************/ 28 | template 29 | inline auto 30 | apply_helper(F&& f, Tuple&& t, std::integer_sequence) 31 | -> decltype(f( 32 | std::forward< 33 | typename std::tuple_element::type>::type 34 | >(std::get(t)) ... ) ) 35 | { 36 | return f( 37 | std::forward< 38 | typename std::tuple_element::type>::type 39 | >(std::get(t)) ... ); 40 | } 41 | 42 | } // namespace detail 43 | 44 | 45 | 46 | /***************************************************************************** 47 | * 48 | * @brief invokes a callable object with a tuple of arguments, 49 | * similar to C++17's std::apply 50 | * 51 | *****************************************************************************/ 52 | template 53 | inline auto 54 | apply(F&& f, std::tuple<>) -> decltype(f()) 55 | { 56 | return f(); 57 | } 58 | 59 | //--------------------------------------------------------- 60 | template 0),F>::type> 62 | inline auto 63 | apply(F&& f, std::tuple& t) 64 | -> decltype(detail::apply_helper(std::forward(f),t,std::make_index_sequence{})) 65 | { 66 | return detail::apply_helper(std::forward(f),t,std::make_index_sequence{}); 67 | } 68 | 69 | //----------------------------------------------------- 70 | template 0),F>::type> 72 | inline auto 73 | apply(F&& f, const std::tuple& t) 74 | -> decltype(detail::apply_helper(std::forward(f),t,std::make_index_sequence{})) 75 | { 76 | return detail::apply_helper(std::forward(f),t,std::make_index_sequence{}); 77 | } 78 | 79 | } // namespace am 80 | 81 | 82 | #endif 83 | --------------------------------------------------------------------------------