15#define THREAD_POOL_VERSION "v2.0.0 (2021-08-14)"
37 typedef std::uint_fast32_t
ui32;
38 typedef std::uint_fast64_t
ui64;
51 :
thread_count(_thread_count ? _thread_count : std::thread::hardware_concurrency()),
threads(new std::thread[_thread_count ? _thread_count : std::thread::hardware_concurrency()]) {
116 template <
typename T1,
typename T2,
typename F>
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)
123 if (last_index < the_first_index) {
125 last_index = the_first_index;
126 the_first_index = temp;
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) {
135 num_blocks = (
ui32)total_size > 1 ? (
ui32)total_size : 1;
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);
142 push_task([start, end, &loop, &blocks_running] {
147 while (blocks_running != 0) {
158 template <
typename F>
163 tasks.push(std::function<
void()>(task));
176 template <
typename F,
typename... A>
178 push_task([task, args...] { task(args...); });
186 void reset(
const ui32 &_thread_count = std::thread::hardware_concurrency()) {
192 thread_count = _thread_count ? _thread_count : std::thread::hardware_concurrency();
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] {
215 task_promise->set_value(true);
218 task_promise->set_exception(std::current_exception());
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] {
242 task_promise->set_value(task(args...));
245 task_promise->set_exception(std::current_exception());
259 if (tasks_total == 0)
262 if (get_tasks_running() == 0)
276 std::atomic<bool> paused =
false;
292 for (
ui32 i = 0; i < thread_count; i++) {
301 for (
ui32 i = 0; i < thread_count; i++) {
313 const std::scoped_lock lock(queue_mutex);
317 task = std::move(tasks.front());
329 std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration));
331 std::this_thread::yield();
339 std::function<void()> task;
340 if (!paused && pop_task(task)) {
356 mutable std::mutex queue_mutex = {};
361 std::atomic<bool> running =
true;
366 std::queue<std::function<void()>> tasks = {};
381 std::atomic<ui32> tasks_total = 0;
409 template <
typename... T>
421 template <
typename... T>
423 print(items...,
'\n');
448 typedef std::int_fast64_t
i64;
455 start_time = std::chrono::steady_clock::now();
471 return (std::chrono::duration_cast<std::chrono::milliseconds>(
elapsed_time)).count();
478 std::chrono::time_point<std::chrono::steady_clock>
start_time = std::chrono::steady_clock::now();
483 std::chrono::duration<double>
elapsed_time = std::chrono::duration<double>::zero();
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.
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,...
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.
~thread_pool()
Destruct the thread pool.
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.
thread_pool(const ui32 &_thread_count=std::thread::hardware_concurrency())
Construct a new thread pool.
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.
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.
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().