3D City v-1.0
Computer Graphics Project
thread_pool.hpp
Go to the documentation of this file.
1#pragma once
2
15#define THREAD_POOL_VERSION "v2.0.0 (2021-08-14)"
16
17#include <atomic> // std::atomic
18#include <chrono> // std::chrono
19#include <cstdint> // std::int_fast64_t, std::uint_fast32_t
20#include <functional> // std::function
21#include <future> // std::future, std::promise
22#include <iostream> // std::cout, std::ostream
23#include <memory> // std::shared_ptr, std::unique_ptr
24#include <mutex> // std::mutex, std::scoped_lock
25#include <queue> // std::queue
26#include <thread> // std::this_thread, std::thread
27#include <type_traits> // std::common_type_t, std::decay_t, std::enable_if_t, std::is_void_v, std::invoke_result_t
28#include <utility> // std::move
29
30// ============================================================================================= //
31// Begin class thread_pool //
32
37 typedef std::uint_fast32_t ui32;
38 typedef std::uint_fast64_t ui64;
39
40 public:
41 // ============================
42 // Constructors and destructors
43 // ============================
44
50 thread_pool(const ui32 &_thread_count = std::thread::hardware_concurrency())
51 : thread_count(_thread_count ? _thread_count : std::thread::hardware_concurrency()), threads(new std::thread[_thread_count ? _thread_count : std::thread::hardware_concurrency()]) {
53 }
54
60 running = false;
62 }
63
64 // =======================
65 // Public member functions
66 // =======================
67
74 const std::scoped_lock lock(queue_mutex);
75 return tasks.size();
76 }
77
85 }
86
93 return tasks_total;
94 }
95
102 return thread_count;
103 }
104
116 template <typename T1, typename T2, typename F>
117 void parallelize_loop(const T1 &first_index, const T2 &index_after_last, const F &loop, ui32 num_blocks = 0) {
118 typedef std::common_type_t<T1, T2> T;
119 T the_first_index = (T)first_index;
120 T last_index = (T)index_after_last;
121 if (the_first_index == last_index)
122 return;
123 if (last_index < the_first_index) {
124 T temp = last_index;
125 last_index = the_first_index;
126 the_first_index = temp;
127 }
128 last_index--;
129 if (num_blocks == 0)
130 num_blocks = thread_count;
131 ui64 total_size = (ui64)(last_index - the_first_index + 1);
132 ui64 block_size = (ui64)(total_size / num_blocks);
133 if (block_size == 0) {
134 block_size = 1;
135 num_blocks = (ui32)total_size > 1 ? (ui32)total_size : 1;
136 }
137 std::atomic<ui32> blocks_running = 0;
138 for (ui32 t = 0; t < num_blocks; t++) {
139 T start = ((T)(t * block_size) + the_first_index);
140 T end = (t == num_blocks - 1) ? last_index + 1 : ((T)((t + 1) * block_size) + the_first_index);
141 blocks_running++;
142 push_task([start, end, &loop, &blocks_running] {
143 loop(start, end);
144 blocks_running--;
145 });
146 }
147 while (blocks_running != 0) {
149 }
150 }
151
158 template <typename F>
159 void push_task(const F &task) {
160 tasks_total++;
161 {
162 const std::scoped_lock lock(queue_mutex);
163 tasks.push(std::function<void()>(task));
164 }
165 }
166
176 template <typename F, typename... A>
177 void push_task(const F &task, const A &...args) {
178 push_task([task, args...] { task(args...); });
179 }
180
186 void reset(const ui32 &_thread_count = std::thread::hardware_concurrency()) {
187 bool was_paused = paused;
188 paused = true;
190 running = false;
192 thread_count = _thread_count ? _thread_count : std::thread::hardware_concurrency();
193 threads.reset(new std::thread[thread_count]);
194 paused = was_paused;
195 running = true;
197 }
198
208 template <typename F, typename... A, typename = std::enable_if_t<std::is_void_v<std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>>>
209 std::future<bool> submit(const F &task, const A &...args) {
210 std::shared_ptr<std::promise<bool>> task_promise(new std::promise<bool>);
211 std::future<bool> future = task_promise->get_future();
212 push_task([task, args..., task_promise] {
213 try {
214 task(args...);
215 task_promise->set_value(true);
216 } catch (...) {
217 try {
218 task_promise->set_exception(std::current_exception());
219 } catch (...) {
220 }
221 }
222 });
223 return future;
224 }
225
236 template <typename F, typename... A, typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>, typename = std::enable_if_t<!std::is_void_v<R>>>
237 std::future<R> submit(const F &task, const A &...args) {
238 std::shared_ptr<std::promise<R>> task_promise(new std::promise<R>);
239 std::future<R> future = task_promise->get_future();
240 push_task([task, args..., task_promise] {
241 try {
242 task_promise->set_value(task(args...));
243 } catch (...) {
244 try {
245 task_promise->set_exception(std::current_exception());
246 } catch (...) {
247 }
248 }
249 });
250 return future;
251 }
252
257 while (true) {
258 if (!paused) {
259 if (tasks_total == 0)
260 break;
261 } else {
262 if (get_tasks_running() == 0)
263 break;
264 }
265 sleep_or_yield();
266 }
267 }
268
269 // ===========
270 // Public data
271 // ===========
272
276 std::atomic<bool> paused = false;
277
281 ui32 sleep_duration = 1000;
282
283 private:
284 // ========================
285 // Private member functions
286 // ========================
287
292 for (ui32 i = 0; i < thread_count; i++) {
293 threads[i] = std::thread(&thread_pool::worker, this);
294 }
295 }
296
301 for (ui32 i = 0; i < thread_count; i++) {
302 threads[i].join();
303 }
304 }
305
312 bool pop_task(std::function<void()> &task) {
313 const std::scoped_lock lock(queue_mutex);
314 if (tasks.empty())
315 return false;
316 else {
317 task = std::move(tasks.front());
318 tasks.pop();
319 return true;
320 }
321 }
322
328 if (sleep_duration)
329 std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration));
330 else
331 std::this_thread::yield();
332 }
333
337 void worker() {
338 while (running) {
339 std::function<void()> task;
340 if (!paused && pop_task(task)) {
341 task();
342 tasks_total--;
343 } else {
344 sleep_or_yield();
345 }
346 }
347 }
348
349 // ============
350 // Private data
351 // ============
352
356 mutable std::mutex queue_mutex = {};
357
361 std::atomic<bool> running = true;
362
366 std::queue<std::function<void()>> tasks = {};
367
372
376 std::unique_ptr<std::thread[]> threads;
377
381 std::atomic<ui32> tasks_total = 0;
382};
383
384// End class thread_pool //
385// ============================================================================================= //
386
387// ============================================================================================= //
388// Begin class synced_stream //
389
394 public:
400 synced_stream(std::ostream &_out_stream = std::cout)
401 : out_stream(_out_stream){};
402
409 template <typename... T>
410 void print(const T &...items) {
411 const std::scoped_lock lock(stream_mutex);
412 (out_stream << ... << items);
413 }
414
421 template <typename... T>
422 void println(const T &...items) {
423 print(items..., '\n');
424 }
425
426 private:
430 mutable std::mutex stream_mutex = {};
431
435 std::ostream &out_stream;
436};
437
438// End class synced_stream //
439// ============================================================================================= //
440
441// ============================================================================================= //
442// Begin class timer //
443
447class timer {
448 typedef std::int_fast64_t i64;
449
450 public:
454 void start() {
455 start_time = std::chrono::steady_clock::now();
456 }
457
461 void stop() {
462 elapsed_time = std::chrono::steady_clock::now() - start_time;
463 }
464
470 i64 ms() const {
471 return (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time)).count();
472 }
473
474 private:
478 std::chrono::time_point<std::chrono::steady_clock> start_time = std::chrono::steady_clock::now();
479
483 std::chrono::duration<double> elapsed_time = std::chrono::duration<double>::zero();
484};
485
486// End class timer //
487// ============================================================================================= //
A helper class to synchronize printing to an output stream by different threads.
std::mutex stream_mutex
A mutex to synchronize printing.
std::ostream & out_stream
The output stream to print to.
synced_stream(std::ostream &_out_stream=std::cout)
Construct a new synced stream.
void println(const T &...items)
Print any number of items into the output stream, followed by a newline character.
void print(const T &...items)
Print any number of items into the output stream.
A C++17 thread pool class.
Definition: thread_pool.hpp:36
std::queue< std::function< void()> > tasks
A queue of tasks to be executed by the threads.
std::future< bool > submit(const F &task, const A &...args)
Submit a function with zero or more arguments and no return value into the task queue,...
std::atomic< bool > running
An atomic variable indicating to the workers to keep running.
void worker()
A worker function to be assigned to each thread in the pool.
void push_task(const F &task)
Push a function with no arguments or return value into the task queue.
ui32 thread_count
The number of threads in the pool.
std::future< R > submit(const F &task, const A &...args)
Submit a function with zero or more arguments and a return value into the task queue,...
std::unique_ptr< std::thread[]> threads
A smart pointer to manage the memory allocated for the threads.
std::atomic< bool > paused
An atomic variable indicating to the workers to pause.
void destroy_threads()
Destroy the threads in the pool by joining them.
void wait_for_tasks()
Wait for tasks to be completed.
ui32 get_thread_count() const
Get the number of threads in the pool.
void parallelize_loop(const T1 &first_index, const T2 &index_after_last, const F &loop, ui32 num_blocks=0)
Parallelize a loop by splitting it into blocks, submitting each block separately to the thread pool,...
std::uint_fast32_t ui32
Definition: thread_pool.hpp:37
void create_threads()
Create the threads in the pool and assign a worker to each thread.
bool pop_task(std::function< void()> &task)
Try to pop a new task out of the queue.
void reset(const ui32 &_thread_count=std::thread::hardware_concurrency())
Reset the number of threads in the pool.
std::atomic< ui32 > tasks_total
An atomic variable to keep track of the total number of unfinished tasks - either still in the queue,...
ui32 get_tasks_total() const
Get the total number of unfinished tasks - either still in the queue, or running in a thread.
Definition: thread_pool.hpp:92
~thread_pool()
Destruct the thread pool.
Definition: thread_pool.hpp:58
std::uint_fast64_t ui64
Definition: thread_pool.hpp:38
void push_task(const F &task, const A &...args)
Push a function with arguments, but no return value, into the task queue.
ui32 get_tasks_running() const
Get the number of tasks currently being executed by the threads.
Definition: thread_pool.hpp:83
thread_pool(const ui32 &_thread_count=std::thread::hardware_concurrency())
Construct a new thread pool.
Definition: thread_pool.hpp:50
void sleep_or_yield()
Sleep for sleep_duration microseconds.
ui64 get_tasks_queued() const
Get the number of tasks currently waiting in the queue to be executed by the threads.
Definition: thread_pool.hpp:73
std::mutex queue_mutex
A mutex to synchronize access to the task queue by different threads.
A helper class to measure execution time for benchmarking purposes.
std::int_fast64_t i64
i64 ms() const
Get the number of milliseconds that have elapsed between start() and stop().
void stop()
Stop measuring time and store the elapsed time since start().
void start()
Start (or restart) measuring time.
std::chrono::time_point< std::chrono::steady_clock > start_time
The time point when measuring started.
std::chrono::duration< double > elapsed_time
The duration that has elapsed between start() and stop().