mirror of
https://github.com/bshoshany/thread-pool.git
synced 2026-07-22 19:43:00 +04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cca27608ee |
+41
-21
@@ -3,14 +3,14 @@
|
||||
/**
|
||||
* @file BS_thread_pool.hpp
|
||||
* @author Barak Shoshany (baraksh@gmail.com) (http://baraksh.com)
|
||||
* @version 3.0.0
|
||||
* @date 2022-05-30
|
||||
* @copyright Copyright (c) 2022 Barak Shoshany. Licensed under the MIT license. If you use this library in software of any kind, please provide a link to the GitHub repository https://github.com/bshoshany/thread-pool in the source code and documentation. If you use this library in published research, please cite it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance Scientific Computing", doi:10.5281/zenodo.4742687, arXiv:2105.00613 (May 2021)
|
||||
* @version 3.1.0
|
||||
* @date 2022-07-13
|
||||
* @copyright Copyright (c) 2022 Barak Shoshany. Licensed under the MIT license. If you found this project useful, please consider starring it on GitHub! If you use this library in software of any kind, please provide a link to the GitHub repository https://github.com/bshoshany/thread-pool in the source code and documentation. If you use this library in published research, please cite it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance Scientific Computing", doi:10.5281/zenodo.4742687, arXiv:2105.00613 (May 2021)
|
||||
*
|
||||
* @brief BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library. This header file contains the entire library, including the main BS::thread_pool class and the helper classes BS::multi_future, BS:synced_stream, and BS::timer.
|
||||
*/
|
||||
|
||||
#define BS_THREAD_POOL_VERSION "v3.0.0 (2022-05-30)"
|
||||
#define BS_THREAD_POOL_VERSION "v3.1.0 (2022-07-13)"
|
||||
|
||||
#include <atomic> // std::atomic
|
||||
#include <chrono> // std::chrono
|
||||
@@ -38,7 +38,7 @@ using concurrency_t = std::invoke_result_t<decltype(std::thread::hardware_concur
|
||||
* @brief A helper class to facilitate waiting for and/or getting the results of multiple futures at once.
|
||||
*/
|
||||
template <typename T>
|
||||
class multi_future
|
||||
class [[nodiscard]] multi_future
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@@ -46,14 +46,14 @@ public:
|
||||
*
|
||||
* @param num_futures_ The desired number of futures to store.
|
||||
*/
|
||||
explicit multi_future(const size_t num_futures_ = 0) : f(num_futures_) {}
|
||||
multi_future(const size_t num_futures_ = 0) : f(num_futures_) {}
|
||||
|
||||
/**
|
||||
* @brief Get the results from all the futures stored in this multi_future object.
|
||||
*
|
||||
* @return A vector containing the results.
|
||||
*/
|
||||
std::vector<T> get()
|
||||
[[nodiscard]] std::vector<T> get()
|
||||
{
|
||||
std::vector<T> results(f.size());
|
||||
for (size_t i = 0; i < f.size(); ++i)
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
/**
|
||||
* @brief A fast, lightweight, and easy-to-use C++17 thread pool class.
|
||||
*/
|
||||
class thread_pool
|
||||
class [[nodiscard]] thread_pool
|
||||
{
|
||||
public:
|
||||
// ============================
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
*
|
||||
* @param thread_count_ The number of threads to use. The default value is the total number of hardware threads available, as reported by the implementation. This is usually determined by the number of cores in the CPU. If a core is hyperthreaded, it will count as two threads.
|
||||
*/
|
||||
explicit thread_pool(const concurrency_t thread_count_ = std::thread::hardware_concurrency()) : thread_count(thread_count_ ? thread_count_ : std::thread::hardware_concurrency()), threads(std::make_unique<std::thread[]>(thread_count_ ? thread_count_ : std::thread::hardware_concurrency()))
|
||||
thread_pool(const concurrency_t thread_count_ = 0) : thread_count(determine_thread_count(thread_count_)), threads(std::make_unique<std::thread[]>(determine_thread_count(thread_count_)))
|
||||
{
|
||||
create_threads();
|
||||
}
|
||||
@@ -120,7 +120,7 @@ public:
|
||||
*
|
||||
* @return The number of queued tasks.
|
||||
*/
|
||||
size_t get_tasks_queued() const
|
||||
[[nodiscard]] size_t get_tasks_queued() const
|
||||
{
|
||||
const std::scoped_lock tasks_lock(tasks_mutex);
|
||||
return tasks.size();
|
||||
@@ -131,7 +131,7 @@ public:
|
||||
*
|
||||
* @return The number of running tasks.
|
||||
*/
|
||||
size_t get_tasks_running() const
|
||||
[[nodiscard]] size_t get_tasks_running() const
|
||||
{
|
||||
const std::scoped_lock tasks_lock(tasks_mutex);
|
||||
return tasks_total - tasks.size();
|
||||
@@ -142,7 +142,7 @@ public:
|
||||
*
|
||||
* @return The total number of tasks.
|
||||
*/
|
||||
size_t get_tasks_total() const
|
||||
[[nodiscard]] size_t get_tasks_total() const
|
||||
{
|
||||
return tasks_total;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public:
|
||||
*
|
||||
* @return The number of threads.
|
||||
*/
|
||||
concurrency_t get_thread_count() const
|
||||
[[nodiscard]] concurrency_t get_thread_count() const
|
||||
{
|
||||
return thread_count;
|
||||
}
|
||||
@@ -172,7 +172,7 @@ public:
|
||||
* @return A multi_future object that can be used to wait for all the blocks to finish. If the loop function returns a value, the multi_future object can be used to obtain the values returned by each block.
|
||||
*/
|
||||
template <typename F, typename T1, typename T2, typename T = std::common_type_t<T1, T2>, typename R = std::invoke_result_t<std::decay_t<F>, T, T>>
|
||||
multi_future<R> parallelize_loop(const T1& first_index, const T2& index_after_last, const F& loop, size_t num_blocks = 0)
|
||||
[[nodiscard]] multi_future<R> parallelize_loop(const T1& first_index, const T2& index_after_last, const F& loop, size_t num_blocks = 0)
|
||||
{
|
||||
T first_index_T = static_cast<T>(first_index);
|
||||
T index_after_last_T = static_cast<T>(index_after_last);
|
||||
@@ -226,13 +226,13 @@ public:
|
||||
*
|
||||
* @param thread_count_ The number of threads to use. The default value is the total number of hardware threads available, as reported by the implementation. This is usually determined by the number of cores in the CPU. If a core is hyperthreaded, it will count as two threads.
|
||||
*/
|
||||
void reset(const concurrency_t thread_count_ = std::thread::hardware_concurrency())
|
||||
void reset(const concurrency_t thread_count_ = 0)
|
||||
{
|
||||
const bool was_paused = paused;
|
||||
paused = true;
|
||||
wait_for_tasks();
|
||||
destroy_threads();
|
||||
thread_count = thread_count_ ? thread_count_ : std::thread::hardware_concurrency();
|
||||
thread_count = determine_thread_count(thread_count_);
|
||||
threads = std::make_unique<std::thread[]>(thread_count);
|
||||
paused = was_paused;
|
||||
create_threads();
|
||||
@@ -249,7 +249,7 @@ public:
|
||||
* @return A future to be used later to wait for the function to finish executing and/or obtain its returned value if it has one.
|
||||
*/
|
||||
template <typename F, typename... A, typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>
|
||||
std::future<R> submit(const F& task, const A&... args)
|
||||
[[nodiscard]] std::future<R> submit(const F& task, const A&... args)
|
||||
{
|
||||
std::shared_ptr<std::promise<R>> task_promise = std::make_shared<std::promise<R>>();
|
||||
push_task(
|
||||
@@ -331,6 +331,25 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determine how many threads the pool should have, based on the parameter passed to the constructor or reset().
|
||||
*
|
||||
* @param thread_count_ The parameter passed to the constructor or reset(). If the parameter is a positive number, then the pool will be created with this number of threads. If the parameter is non-positive, or a parameter was not supplied (in which case it will have the default value of 0), then the pool will be created with the total number of hardware threads available, as obtained from std::thread::hardware_concurrency(). If the latter returns a non-positive number for some reason, then the pool will be created with just one thread.
|
||||
* @return The number of threads to use for constructing the pool.
|
||||
*/
|
||||
[[nodiscard]] concurrency_t determine_thread_count(const concurrency_t thread_count_)
|
||||
{
|
||||
if (thread_count_ > 0)
|
||||
return thread_count_;
|
||||
else
|
||||
{
|
||||
if (std::thread::hardware_concurrency() > 0)
|
||||
return std::thread::hardware_concurrency();
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A worker function to be assigned to each thread in the pool. Waits until it is notified by push_task() that a task is available, and then retrieves the task from the queue and executes it. Once the task finishes, the worker notifies wait_for_tasks() in case it is waiting.
|
||||
*/
|
||||
@@ -347,6 +366,7 @@ private:
|
||||
tasks.pop();
|
||||
tasks_lock.unlock();
|
||||
task();
|
||||
tasks_lock.lock();
|
||||
--tasks_total;
|
||||
if (waiting)
|
||||
task_done_cv.notify_one();
|
||||
@@ -413,7 +433,7 @@ private:
|
||||
/**
|
||||
* @brief A helper class to synchronize printing to an output stream by different threads.
|
||||
*/
|
||||
class synced_stream
|
||||
class [[nodiscard]] synced_stream
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@@ -421,7 +441,7 @@ public:
|
||||
*
|
||||
* @param out_stream_ The output stream to print to. The default value is std::cout.
|
||||
*/
|
||||
explicit synced_stream(std::ostream& out_stream_ = std::cout) : out_stream(out_stream_) {};
|
||||
synced_stream(std::ostream& out_stream_ = std::cout) : out_stream(out_stream_) {}
|
||||
|
||||
/**
|
||||
* @brief Print any number of items into the output stream. Ensures that no other threads print to this stream simultaneously, as long as they all exclusively use the same synced_stream object to print.
|
||||
@@ -469,7 +489,7 @@ private:
|
||||
/**
|
||||
* @brief A helper class to measure execution time for benchmarking purposes.
|
||||
*/
|
||||
class timer
|
||||
class [[nodiscard]] timer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@@ -493,7 +513,7 @@ public:
|
||||
*
|
||||
* @return The number of milliseconds.
|
||||
*/
|
||||
std::chrono::milliseconds::rep ms() const
|
||||
[[nodiscard]] std::chrono::milliseconds::rep ms() const
|
||||
{
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time)).count();
|
||||
}
|
||||
|
||||
+105
-55
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* @file BS_thread_pool_test.cpp
|
||||
* @author Barak Shoshany (baraksh@gmail.com) (http://baraksh.com)
|
||||
* @version 3.0.0
|
||||
* @date 2022-05-30
|
||||
* @copyright Copyright (c) 2022 Barak Shoshany. Licensed under the MIT license. If you use this library in software of any kind, please provide a link to the GitHub repository https://github.com/bshoshany/thread-pool in the source code and documentation. If you use this library in published research, please cite it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance Scientific Computing", doi:10.5281/zenodo.4742687, arXiv:2105.00613 (May 2021)
|
||||
* @version 3.1.0
|
||||
* @date 2022-07-13
|
||||
* @copyright Copyright (c) 2022 Barak Shoshany. Licensed under the MIT license. If you found this project useful, please consider starring it on GitHub! If you use this library in software of any kind, please provide a link to the GitHub repository https://github.com/bshoshany/thread-pool in the source code and documentation. If you use this library in published research, please cite it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance Scientific Computing", doi:10.5281/zenodo.4742687, arXiv:2105.00613 (May 2021)
|
||||
*
|
||||
* @brief BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library. This program tests all aspects of the library, but is not needed in order to use the library.
|
||||
*/
|
||||
@@ -13,24 +13,26 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <algorithm> // std::min, std::min_element, std::sort, std::unique
|
||||
#include <atomic> // std::atomic
|
||||
#include <chrono> // std::chrono
|
||||
#include <cmath> // std::abs, std::llround, std::round, std::sqrt
|
||||
#include <ctime> // std::localtime, std::strftime, std::time_t
|
||||
#include <exception> // std::exception
|
||||
#include <fstream> // std::ofstream
|
||||
#include <future> // std::future
|
||||
#include <iomanip> // std::setprecision, std::setw
|
||||
#include <ios> // std::fixed
|
||||
#include <iostream> // std::cout
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <random> // std::mt19937_64, std::random_device, std::uniform_int_distribution, std::uniform_real_distribution
|
||||
#include <stdexcept> // std::runtime_error
|
||||
#include <string> // std::string, std::to_string
|
||||
#include <thread> // std::this_thread, std::thread
|
||||
#include <utility> // std::pair
|
||||
#include <vector> // std::begin, std::end, std::vector
|
||||
#include <algorithm> // std::min, std::min_element, std::sort, std::unique
|
||||
#include <atomic> // std::atomic
|
||||
#include <chrono> // std::chrono
|
||||
#include <cmath> // std::abs, std::llround, std::round, std::sqrt
|
||||
#include <condition_variable> // std::condition_variable
|
||||
#include <ctime> // std::localtime, std::strftime, std::time_t
|
||||
#include <exception> // std::exception
|
||||
#include <fstream> // std::ofstream
|
||||
#include <future> // std::future
|
||||
#include <iomanip> // std::setprecision, std::setw
|
||||
#include <ios> // std::fixed
|
||||
#include <iostream> // std::cout
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <mutex> // std::mutex, std::scoped_lock, std::unique_lock
|
||||
#include <random> // std::mt19937_64, std::random_device, std::uniform_int_distribution, std::uniform_real_distribution
|
||||
#include <stdexcept> // std::runtime_error
|
||||
#include <string> // std::string, std::to_string
|
||||
#include <thread> // std::this_thread, std::thread
|
||||
#include <utility> // std::pair
|
||||
#include <vector> // std::begin, std::end, std::vector
|
||||
|
||||
// Include the header file for the thread pool library.
|
||||
#include "BS_thread_pool.hpp"
|
||||
@@ -39,13 +41,13 @@
|
||||
// Global variables
|
||||
// ================
|
||||
|
||||
// Set to false to disable output to a log file.
|
||||
// Whether to output to a log file in addition to the standard output.
|
||||
constexpr bool output_log = true;
|
||||
|
||||
// Set to false to disable testing.
|
||||
// Whether to perform the tests.
|
||||
constexpr bool enable_tests = true;
|
||||
|
||||
// Set to false to disable the benchmarks.
|
||||
// Whether to perform the benchmarks.
|
||||
constexpr bool enable_benchmarks = true;
|
||||
|
||||
// Two global synced_streams objects. One prints to std::cout, and the other to a file.
|
||||
@@ -139,34 +141,66 @@ void check(const bool condition)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the expected result has been obtained, report the result, and keep count of the total number of successes and failures.
|
||||
*
|
||||
* @param condition The condition to check.
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
void check(const T1 expected, const T2 obtained)
|
||||
{
|
||||
dual_print("Expected: ", expected, ", obtained: ", obtained);
|
||||
if (expected == obtained)
|
||||
{
|
||||
dual_println(" -> PASSED!");
|
||||
++tests_succeeded;
|
||||
}
|
||||
else
|
||||
{
|
||||
dual_println(" -> FAILED!");
|
||||
++tests_failed;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// Functions to verify the number of threads
|
||||
// =========================================
|
||||
|
||||
/**
|
||||
* @brief Store the ID of the current thread in memory. Waits for a short time to ensure it does not get evaluated by more than one thread.
|
||||
*
|
||||
* @param location A pointer to the location where the thread ID should be stored.
|
||||
*/
|
||||
void store_ID(std::thread::id* location)
|
||||
{
|
||||
*location = std::this_thread::get_id();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Count the number of unique threads in the thread pool to ensure that the correct number of individual threads was created. Pushes a number of tasks equal to four times the thread count into the thread pool, and count the number of unique thread IDs returned by the tasks.
|
||||
* @brief Count the number of unique threads in the pool. Submits a number of tasks equal to twice the thread count into the pool. Each task stores the ID of the thread running it, and then waits until released by the main thread. This ensures that each thread in the pool runs at least one task. The number of unique thread IDs is then counted from the stored IDs.
|
||||
*/
|
||||
BS::concurrency_t count_unique_threads()
|
||||
{
|
||||
std::vector<std::thread::id> thread_IDs(pool.get_thread_count() * 4);
|
||||
BS::multi_future<void> futures;
|
||||
const BS::concurrency_t num_tasks = pool.get_thread_count() * 2;
|
||||
std::vector<std::thread::id> thread_IDs(num_tasks);
|
||||
std::mutex ID_mutex, total_mutex;
|
||||
std::condition_variable ID_cv, total_cv;
|
||||
std::unique_lock<std::mutex> total_lock(total_mutex);
|
||||
BS::concurrency_t total_count = 0;
|
||||
bool ID_release = false;
|
||||
pool.wait_for_tasks();
|
||||
for (std::thread::id& id : thread_IDs)
|
||||
futures.f.push_back(pool.submit(store_ID, &id));
|
||||
futures.wait();
|
||||
pool.push_task(
|
||||
[&]
|
||||
{
|
||||
id = std::this_thread::get_id();
|
||||
{
|
||||
const std::scoped_lock total_lock_local(total_mutex);
|
||||
++total_count;
|
||||
}
|
||||
total_cv.notify_one();
|
||||
std::unique_lock<std::mutex> ID_lock_local(ID_mutex);
|
||||
ID_cv.wait(ID_lock_local, [&] { return ID_release; });
|
||||
});
|
||||
total_cv.wait_for(total_lock, std::chrono::milliseconds(500), [&] { return total_count == pool.get_thread_count(); });
|
||||
{
|
||||
const std::scoped_lock ID_lock(ID_mutex);
|
||||
ID_release = true;
|
||||
}
|
||||
ID_cv.notify_all();
|
||||
total_cv.wait_for(total_lock, std::chrono::milliseconds(500), [&] { return total_count == num_tasks; });
|
||||
std::sort(thread_IDs.begin(), thread_IDs.end());
|
||||
BS::concurrency_t unique_threads = (BS::concurrency_t)(std::unique(thread_IDs.begin(), thread_IDs.end()) - thread_IDs.begin());
|
||||
return unique_threads;
|
||||
return static_cast<BS::concurrency_t>(std::unique(thread_IDs.begin(), thread_IDs.end()) - thread_IDs.begin());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,9 +209,9 @@ BS::concurrency_t count_unique_threads()
|
||||
void check_constructor()
|
||||
{
|
||||
dual_println("Checking that the thread pool reports a number of threads equal to the hardware concurrency...");
|
||||
check(pool.get_thread_count() == std::thread::hardware_concurrency());
|
||||
check(std::thread::hardware_concurrency(), pool.get_thread_count());
|
||||
dual_println("Checking that the manually counted number of unique thread IDs is equal to the reported number of threads...");
|
||||
check(pool.get_thread_count() == count_unique_threads());
|
||||
check(pool.get_thread_count(), count_unique_threads());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,14 +221,14 @@ void check_reset()
|
||||
{
|
||||
pool.reset(std::thread::hardware_concurrency() / 2);
|
||||
dual_println("Checking that after reset() the thread pool reports a number of threads equal to half the hardware concurrency...");
|
||||
check(pool.get_thread_count() == std::thread::hardware_concurrency() / 2);
|
||||
check(std::thread::hardware_concurrency() / 2, pool.get_thread_count());
|
||||
dual_println("Checking that after reset() the manually counted number of unique thread IDs is equal to the reported number of threads...");
|
||||
check(pool.get_thread_count() == count_unique_threads());
|
||||
check(pool.get_thread_count(), count_unique_threads());
|
||||
pool.reset(std::thread::hardware_concurrency());
|
||||
dual_println("Checking that after a second reset() the thread pool reports a number of threads equal to the hardware concurrency...");
|
||||
check(pool.get_thread_count() == std::thread::hardware_concurrency());
|
||||
check(std::thread::hardware_concurrency(), pool.get_thread_count());
|
||||
dual_println("Checking that after a second reset() the manually counted number of unique thread IDs is equal to the reported number of threads...");
|
||||
check(pool.get_thread_count() == count_unique_threads());
|
||||
check(pool.get_thread_count(), count_unique_threads());
|
||||
}
|
||||
|
||||
// =======================================
|
||||
@@ -298,7 +332,7 @@ void check_submit()
|
||||
void check_wait_for_tasks()
|
||||
{
|
||||
const BS::concurrency_t n = pool.get_thread_count() * 10;
|
||||
std::vector<std::atomic<bool>> flags(n);
|
||||
std::unique_ptr<std::atomic<bool>[]> flags = std::make_unique<std::atomic<bool>[]>(n);
|
||||
for (BS::concurrency_t i = 0; i < n; ++i)
|
||||
pool.push_task(
|
||||
[&flags, i]
|
||||
@@ -306,6 +340,7 @@ void check_wait_for_tasks()
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
flags[i] = true;
|
||||
});
|
||||
dual_println("Waiting for tasks...");
|
||||
pool.wait_for_tasks();
|
||||
bool all_flags = true;
|
||||
for (BS::concurrency_t i = 0; i < n; ++i)
|
||||
@@ -331,13 +366,13 @@ void check_parallelize_loop_no_return(const int64_t random_start, int64_t random
|
||||
dual_println("Verifying that a loop from ", random_start, " to ", random_end, " with ", num_tasks, num_tasks == 1 ? " task" : " tasks", " modifies all indices...");
|
||||
const size_t num_indices = static_cast<size_t>(std::abs(random_end - random_start));
|
||||
const int64_t offset = std::min(random_start, random_end);
|
||||
std::vector<std::atomic<bool>> flags(num_indices);
|
||||
std::unique_ptr<std::atomic<bool>[]> flags = std::make_unique<std::atomic<bool>[]>(num_indices);
|
||||
pool.parallelize_loop(
|
||||
random_start, random_end,
|
||||
[&flags, offset](const int64_t start, const int64_t end)
|
||||
{
|
||||
for (int64_t i = start; i < end; ++i)
|
||||
flags[(size_t)(i - offset)] = true;
|
||||
flags[static_cast<size_t>(i - offset)] = true;
|
||||
},
|
||||
num_tasks)
|
||||
.wait();
|
||||
@@ -373,7 +408,7 @@ void check_parallelize_loop_return(const int64_t random_start, int64_t random_en
|
||||
int64_t sum = 0;
|
||||
for (const int64_t& s : sums_vector)
|
||||
sum += s;
|
||||
check(sum * 2 == std::abs(random_start - random_end) * (random_start + random_end - 1));
|
||||
check(std::abs(random_start - random_end) * (random_start + random_end - 1), sum * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,7 +439,7 @@ void check_task_monitoring()
|
||||
dual_println("Resetting pool to ", n, " threads.");
|
||||
pool.reset(n);
|
||||
dual_println("Submitting ", n * 3, " tasks.");
|
||||
std::vector<std::atomic<bool>> release(n * 3);
|
||||
std::unique_ptr<std::atomic<bool>[]> release = std::make_unique<std::atomic<bool>[]>(n * 3);
|
||||
for (BS::concurrency_t i = 0; i < n * 3; ++i)
|
||||
pool.push_task(
|
||||
[&release, i]
|
||||
@@ -416,21 +451,25 @@ void check_task_monitoring()
|
||||
constexpr std::chrono::milliseconds sleep_time(300);
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
dual_println("After submission, should have: ", n * 3, " tasks total, ", n, " tasks running, ", n * 2, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n * 3 && pool.get_tasks_running() == n && pool.get_tasks_queued() == n * 2);
|
||||
for (BS::concurrency_t i = 0; i < n; ++i)
|
||||
release[i] = true;
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
dual_println("After releasing ", n, " tasks, should have: ", n * 2, " tasks total, ", n, " tasks running, ", n, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n * 2 && pool.get_tasks_running() == n && pool.get_tasks_queued() == n);
|
||||
for (BS::concurrency_t i = n; i < n * 2; ++i)
|
||||
release[i] = true;
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
dual_println("After releasing ", n, " more tasks, should have: ", n, " tasks total, ", n, " tasks running, ", 0, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n && pool.get_tasks_running() == n && pool.get_tasks_queued() == 0);
|
||||
for (BS::concurrency_t i = n * 2; i < n * 3; ++i)
|
||||
release[i] = true;
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
dual_println("After releasing the final ", n, " tasks, should have: ", 0, " tasks total, ", 0, " tasks running, ", 0, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == 0 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == 0);
|
||||
dual_println("Resetting pool to ", std::thread::hardware_concurrency(), " threads.");
|
||||
pool.reset(std::thread::hardware_concurrency());
|
||||
@@ -455,27 +494,33 @@ void check_pausing()
|
||||
dual_println("Task ", i, " done.");
|
||||
});
|
||||
dual_println("Immediately after submission, should have: ", n * 3, " tasks total, ", 0, " tasks running, ", n * 3, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n * 3 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n * 3);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
dual_println("300ms later, should still have: ", n * 3, " tasks total, ", 0, " tasks running, ", n * 3, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n * 3 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n * 3);
|
||||
dual_println("Unpausing pool.");
|
||||
pool.paused = false;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
dual_println("300ms later, should have: ", n * 2, " tasks total, ", n, " tasks running, ", n, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n * 2 && pool.get_tasks_running() == n && pool.get_tasks_queued() == n);
|
||||
dual_println("Pausing pool and using wait_for_tasks() to wait for the running tasks.");
|
||||
pool.paused = true;
|
||||
pool.wait_for_tasks();
|
||||
dual_println("After waiting, should have: ", n, " tasks total, ", 0, " tasks running, ", n, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
dual_println("200ms later, should still have: ", n, " tasks total, ", 0, " tasks running, ", n, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == n && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n);
|
||||
dual_println("Unpausing pool and using wait_for_tasks() to wait for all tasks.");
|
||||
pool.paused = false;
|
||||
pool.wait_for_tasks();
|
||||
dual_println("After waiting, should have: ", 0, " tasks total, ", 0, " tasks running, ", 0, " tasks queued...");
|
||||
dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued ");
|
||||
check(pool.get_tasks_total() == 0 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == 0);
|
||||
dual_println("Resetting pool to ", std::thread::hardware_concurrency(), " threads.");
|
||||
pool.reset(std::thread::hardware_concurrency());
|
||||
@@ -491,7 +536,12 @@ void check_pausing()
|
||||
void check_exceptions()
|
||||
{
|
||||
bool caught = false;
|
||||
std::future<void> my_future = pool.submit([] { throw std::runtime_error("Exception thrown!"); });
|
||||
std::future<void> my_future = pool.submit(
|
||||
[]
|
||||
{
|
||||
dual_println("Throwing exception...");
|
||||
throw std::runtime_error("Exception thrown!");
|
||||
});
|
||||
try
|
||||
{
|
||||
my_future.get();
|
||||
@@ -766,7 +816,7 @@ int main()
|
||||
if (output_log)
|
||||
log_file.open(log_filename);
|
||||
|
||||
dual_println("A C++17 Thread Pool for High-Performance Scientific Computing");
|
||||
dual_println("BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library");
|
||||
dual_println("(c) 2022 Barak Shoshany (baraksh@gmail.com) (http://baraksh.com)");
|
||||
dual_println("GitHub: https://github.com/bshoshany/thread-pool\n");
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
By Barak Shoshany ([baraksh@gmail.com](mailto:baraksh@gmail.com)) ([https://baraksh.com/](https://baraksh.com/))
|
||||
|
||||
* [Version history](#version-history)
|
||||
* [v3.1.0 (2022-07-13)](#v310-2022-07-13)
|
||||
* [v3.0.0 (2022-05-30)](#v300-2022-05-30)
|
||||
* [v2.0.0 (2021-08-14)](#v200-2021-08-14)
|
||||
* [v1.9 (2021-07-29)](#v19-2021-07-29)
|
||||
@@ -28,6 +29,23 @@ By Barak Shoshany ([baraksh@gmail.com](mailto:baraksh@gmail.com)) ([https://bara
|
||||
|
||||
## Version history
|
||||
|
||||
### v3.1.0 (2022-07-13)
|
||||
|
||||
* `BS_thread_pool.hpp`:
|
||||
* Fixed an issue where `wait_for_tasks()` would sometimes get stuck if `push_task()` was executed immediately before `wait_for_tasks()`.
|
||||
* Both the thread pool constructor and the `reset()` member function now determine the number of threads to use in the pool as follows. If the parameter is a positive number, then the pool will be created with this number of threads. If the parameter is non-positive, or a parameter was not supplied, then the pool will be created with the total number of hardware threads available, as obtained from `std::thread::hardware_concurrency()`. If the latter returns a non-positive number for some reason, then the pool will be created with just one thread. See [#51](https://github.com/bshoshany/thread-pool/issues/51) and [#52](https://github.com/bshoshany/thread-pool/issues/52).
|
||||
* Added the `[[nodiscard]]` attribute to classes and class members, in order to warn the user when accidentally discarding an important return value, such as a future or the return value of a function with no useful side-effects. For example, if you use `submit()` and don't save the future it returns, the compiler will now generate a warning. (If a future is not needed, then you should use `push_task()` instead.)
|
||||
* Removed the `explicit` specifier from all constructors, as it prevented the default constructor from being used with static class members. See [#48](https://github.com/bshoshany/thread-pool/issues/48>).
|
||||
* `BS_thread_pool_test.cpp`:
|
||||
* Improved `count_unique_threads()` using condition variables, to ensure that each thread in the pool runs at least one task regardless of how fast it takes to run the tasks.
|
||||
* When appropriate, `check()` now explicitly reports what the obtained result was and what it was expected to be.
|
||||
* `check_task_monitoring()` and `check_pausing()` now explicitly report the results of the monitoring at each step.
|
||||
* Changed all instances of `std::vector<std::atomic<bool>>` to `std::unique_ptr<std::atomic<bool>[]>`. See [#44](https://github.com/bshoshany/thread-pool/issues/44).
|
||||
* Converted a few more C-style casts to C++ cast expressions.
|
||||
* `README.md`:
|
||||
* Added instructions for using this package with the [Conan](https://conan.io/) C/C++ package manager. Please refer to [this package's page on ConanCenter](https://conan.io/center/bshoshany-thread-pool) to learn how to use Conan to include this package in your project with various build systems.
|
||||
* If you found this project useful, please consider [starring it on GitHub](https://github.com/bshoshany/thread-pool/stargazers)! This allows me to see how many people are using my code, and motivates me to keep working to improve it.
|
||||
|
||||
### v3.0.0 (2022-05-30)
|
||||
|
||||
* This is a major new release with many changes and improvements! Please note that code written using previous releases will need to be slightly modified to work with the new release. The changes needed to migrate to the new API are explicitly indicated below for your convenience.
|
||||
|
||||
@@ -10,15 +10,19 @@
|
||||
|
||||
# `BS::thread_pool`: a fast, lightweight, and easy-to-use C++17 thread pool library
|
||||
|
||||
Documentation for v3.0.0 (2022-05-30)
|
||||
By Barak Shoshany<br />
|
||||
Email: [baraksh@gmail.com](mailto:baraksh@gmail.com)<br />
|
||||
Website: [https://baraksh.com/](https://baraksh.com/)<br />
|
||||
GitHub: [https://github.com/bshoshany](https://github.com/bshoshany)<br />
|
||||
|
||||
By Barak Shoshany ([baraksh@gmail.com](mailto:baraksh@gmail.com)) ([https://baraksh.com/](https://baraksh.com/))
|
||||
This is the complete documentation for v3.1.0 of the library, released on 2022-07-13.
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Motivation](#motivation)
|
||||
* [Overview of features](#overview-of-features)
|
||||
* [Compiling and compatibility](#compiling-and-compatibility)
|
||||
* [Installing using vcpkg](#installing-using-vcpkg)
|
||||
* [Installing using Conan](#installing-using-conan)
|
||||
* [Getting started](#getting-started)
|
||||
* [Including the library](#including-the-library)
|
||||
* [Constructors](#constructors)
|
||||
@@ -41,9 +45,11 @@ By Barak Shoshany ([baraksh@gmail.com](mailto:baraksh@gmail.com)) ([https://bara
|
||||
* [Testing the package](#testing-the-package)
|
||||
* [Automated tests](#automated-tests)
|
||||
* [Performance tests](#performance-tests)
|
||||
* [Issue and pull request policy](#issue-and-pull-request-policy)
|
||||
* [Acknowledgements](#acknowledgements)
|
||||
* [Copyright and citing](#copyright-and-citing)
|
||||
* [About the project](#about-the-project)
|
||||
* [Issue and pull request policy](#issue-and-pull-request-policy)
|
||||
* [Acknowledgements](#acknowledgements)
|
||||
* [Starring the repository](#starring-the-repository)
|
||||
* [Copyright and citing](#copyright-and-citing)
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -97,11 +103,10 @@ Other, more advanced multithreading libraries may offer more features and/or hig
|
||||
|
||||
This library should successfully compile on any C++17 standard-compliant compiler, on all operating systems and architectures for which such a compiler is available. Compatibility was verified with a 12-core / 24-thread AMD Ryzen 9 3900X CPU using the following compilers and platforms:
|
||||
|
||||
* Windows 11 build 22000.675:
|
||||
* Windows 11 build 22000.795:
|
||||
* [GCC](https://gcc.gnu.org/) v12.1.0 ([WinLibs build](https://winlibs.com/))
|
||||
* [Clang](https://clang.llvm.org/) v14.0.4
|
||||
* [Intel oneAPI C++ Compiler](https://software.intel.com/content/www/us/en/develop/tools/oneapi/components/dpc-compiler.html) v2022.1.0
|
||||
* [MSVC](https://docs.microsoft.com/en-us/cpp/) v19.32.31329
|
||||
* [Clang](https://clang.llvm.org/) v14.0.6
|
||||
* [MSVC](https://docs.microsoft.com/en-us/cpp/) v19.32.31332
|
||||
* Ubuntu 22.04 LTS:
|
||||
* [GCC](https://gcc.gnu.org/) v12.0.1
|
||||
* [Clang](https://clang.llvm.org/) v14.0.0
|
||||
@@ -123,6 +128,7 @@ For maximum performance, it is recommended to compile with all available compile
|
||||
* For MSVC, use `/O2`.
|
||||
|
||||
As an example, to compile the test program `BS_thread_pool_test.cpp` with warnings and optimizations, it is recommended to use the following commands:
|
||||
|
||||
* On Windows with MSVC: `cl BS_thread_pool_test.cpp /std:c++17 /permissive- /O2 /W4 /EHsc /Fe:BS_thread_pool_test.exe`
|
||||
* On Linux with GCC: `g++ BS_thread_pool_test.cpp -std=c++17 -O3 -Wall -Wextra -Wconversion -Wsign-conversion -Wpedantic -Weffc++ -Wshadow -pthread -o BS_thread_pool_test`
|
||||
|
||||
@@ -146,6 +152,10 @@ The thread pool will then be available automatically in the build system you int
|
||||
|
||||
Please see the [vcpkg repository](https://github.com/microsoft/vcpkg) for more information on how to use vcpkg.
|
||||
|
||||
### Installing using Conan
|
||||
|
||||
If you are using the [Conan](https://conan.io/) C/C++ package manager, please refer to [this package's page on ConanCenter](https://conan.io/center/bshoshany-thread-pool) to learn how to use Conan to include this package in your project with various build systems.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Including the library
|
||||
@@ -197,7 +207,7 @@ std::cout << "Thread pool library version is " << BS_THREAD_POOL_VERSION << ".\n
|
||||
Sample output:
|
||||
|
||||
```none
|
||||
Thread pool library version is v3.0.0 (2022-05-30).
|
||||
Thread pool library version is v3.1.0 (2022-07-13).
|
||||
```
|
||||
|
||||
This can be used, for example, to allow the same code to work with several incompatible versions of the library.
|
||||
@@ -837,13 +847,13 @@ If any of the tests fail, please [submit a bug report](https://github.com/bshosh
|
||||
A sample output of a successful run of the automated tests is as follows:
|
||||
|
||||
```none
|
||||
A C++17 Thread Pool for High-Performance Scientific Computing
|
||||
BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library
|
||||
(c) 2022 Barak Shoshany (baraksh@gmail.com) (http://baraksh.com)
|
||||
GitHub: https://github.com/bshoshany/thread-pool
|
||||
|
||||
Thread pool library version is v3.0.0 (2022-05-30).
|
||||
Thread pool library version is v3.1.0 (2022-07-13).
|
||||
Hardware concurrency is 24.
|
||||
Generating log file: BS_thread_pool_test-2022-05-30_22.59.30.log.
|
||||
Generating log file: BS_thread_pool_test-2022-07-13_19.39.37.log.
|
||||
|
||||
Important: Please do not run any other applications, especially multithreaded applications, in parallel with this test!
|
||||
|
||||
@@ -851,21 +861,21 @@ Important: Please do not run any other applications, especially multithreaded ap
|
||||
Checking that the constructor works:
|
||||
====================================
|
||||
Checking that the thread pool reports a number of threads equal to the hardware concurrency...
|
||||
-> PASSED!
|
||||
Expected: 24, obtained: 24 -> PASSED!
|
||||
Checking that the manually counted number of unique thread IDs is equal to the reported number of threads...
|
||||
-> PASSED!
|
||||
Expected: 24, obtained: 24 -> PASSED!
|
||||
|
||||
============================
|
||||
Checking that reset() works:
|
||||
============================
|
||||
Checking that after reset() the thread pool reports a number of threads equal to half the hardware concurrency...
|
||||
-> PASSED!
|
||||
Expected: 12, obtained: 12 -> PASSED!
|
||||
Checking that after reset() the manually counted number of unique thread IDs is equal to the reported number of threads...
|
||||
-> PASSED!
|
||||
Expected: 12, obtained: 12 -> PASSED!
|
||||
Checking that after a second reset() the thread pool reports a number of threads equal to the hardware concurrency...
|
||||
-> PASSED!
|
||||
Expected: 24, obtained: 24 -> PASSED!
|
||||
Checking that after a second reset() the manually counted number of unique thread IDs is equal to the reported number of threads...
|
||||
-> PASSED!
|
||||
Expected: 24, obtained: 24 -> PASSED!
|
||||
|
||||
================================
|
||||
Checking that push_task() works:
|
||||
@@ -896,51 +906,52 @@ Checking that submit() works for a function with two arguments and a return valu
|
||||
=======================================
|
||||
Checking that wait_for_tasks() works...
|
||||
=======================================
|
||||
Waiting for tasks...
|
||||
-> PASSED!
|
||||
|
||||
=======================================
|
||||
Checking that parallelize_loop() works:
|
||||
=======================================
|
||||
Verifying that a loop from -434827 to 461429 with 23 tasks modifies all indices...
|
||||
Verifying that a loop from 839486 to 578526 with 7 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 255333 to -889028 with 9 tasks modifies all indices...
|
||||
Verifying that a loop from 913636 to 945504 with 22 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -257322 to 550471 with 5 tasks modifies all indices...
|
||||
Verifying that a loop from 963330 to 578092 with 14 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -257648 to -475958 with 23 tasks modifies all indices...
|
||||
Verifying that a loop from -520582 to -565322 with 9 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 175412 to -544672 with 13 tasks modifies all indices...
|
||||
Verifying that a loop from 374827 to 785635 with 12 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -244797 to -970178 with 11 tasks modifies all indices...
|
||||
Verifying that a loop from 453412 to 368063 with 7 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 411251 to -718341 with 15 tasks modifies all indices...
|
||||
Verifying that a loop from 557509 to -437850 with 18 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 418787 to 978302 with 22 tasks modifies all indices...
|
||||
Verifying that a loop from -11443 to 743510 with 2 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -2412 to -310158 with 4 tasks modifies all indices...
|
||||
Verifying that a loop from -724659 to -27497 with 20 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -881862 to 137673 with 7 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 539438 to -759983 with 17 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 745706 to -519554 with 13 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 288078 to 432534 with 12 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -487251 to 302796 with 4 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 408766 to 756890 with 3 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -976768 to -500744 with 10 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -817442 to 175967 with 6 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -765007 to -53682 with 23 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from -903190 to -361760 with 14 tasks correctly sums all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 72823 to -85485 with 15 tasks correctly sums all indices...
|
||||
Verifying that a loop from -790047 to -124851 with 18 tasks modifies all indices...
|
||||
-> PASSED!
|
||||
Verifying that a loop from 800644 to 589779 with 24 tasks correctly sums all indices...
|
||||
Expected: 293191335030, obtained: 293191335030 -> PASSED!
|
||||
Verifying that a loop from 74965 to 444791 with 23 tasks correctly sums all indices...
|
||||
Expected: 192218912630, obtained: 192218912630 -> PASSED!
|
||||
Verifying that a loop from 480099 to -851639 with 22 tasks correctly sums all indices...
|
||||
Expected: -494795268258, obtained: -494795268258 -> PASSED!
|
||||
Verifying that a loop from 213814 to 751751 with 15 tasks correctly sums all indices...
|
||||
Expected: 519412601468, obtained: 519412601468 -> PASSED!
|
||||
Verifying that a loop from 839817 to 51524 with 8 tasks correctly sums all indices...
|
||||
Expected: 702637082620, obtained: 702637082620 -> PASSED!
|
||||
Verifying that a loop from -736549 to -495131 with 21 tasks correctly sums all indices...
|
||||
Expected: -297349963658, obtained: -297349963658 -> PASSED!
|
||||
Verifying that a loop from -581483 to 982787 with 20 tasks correctly sums all indices...
|
||||
Expected: 627746243810, obtained: 627746243810 -> PASSED!
|
||||
Verifying that a loop from -991418 to -777388 with 21 tasks correctly sums all indices...
|
||||
Expected: -378577762210, obtained: -378577762210 -> PASSED!
|
||||
Verifying that a loop from -917643 to -429950 with 1 task correctly sums all indices...
|
||||
Expected: -657212160642, obtained: -657212160642 -> PASSED!
|
||||
Verifying that a loop from -473648 to -218320 with 18 tasks correctly sums all indices...
|
||||
Expected: -176679060832, obtained: -176679060832 -> PASSED!
|
||||
|
||||
====================================
|
||||
Checking that task monitoring works:
|
||||
@@ -948,25 +959,25 @@ Checking that task monitoring works:
|
||||
Resetting pool to 4 threads.
|
||||
Submitting 12 tasks.
|
||||
After submission, should have: 12 tasks total, 4 tasks running, 8 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 12 tasks total, 4 tasks running, 8 tasks queued -> PASSED!
|
||||
Task 0 released.
|
||||
Task 2 released.
|
||||
Task 1 released.
|
||||
Task 3 released.
|
||||
After releasing 4 tasks, should have: 8 tasks total, 4 tasks running, 4 tasks queued...
|
||||
-> PASSED!
|
||||
Task 7 released.
|
||||
Result: 8 tasks total, 4 tasks running, 4 tasks queued -> PASSED!
|
||||
Task 5 released.
|
||||
Task 4 released.
|
||||
Task 7 released.
|
||||
Task 6 released.
|
||||
Task 4 released.
|
||||
After releasing 4 more tasks, should have: 4 tasks total, 4 tasks running, 0 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 4 tasks total, 4 tasks running, 0 tasks queued -> PASSED!
|
||||
Task 10 released.
|
||||
Task 8 released.
|
||||
Task 9 released.
|
||||
Task 11 released.
|
||||
Task 8 released.
|
||||
After releasing the final 4 tasks, should have: 0 tasks total, 0 tasks running, 0 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 0 tasks total, 0 tasks running, 0 tasks queued -> PASSED!
|
||||
Resetting pool to 24 threads.
|
||||
|
||||
============================
|
||||
@@ -976,61 +987,62 @@ Resetting pool to 4 threads.
|
||||
Pausing pool.
|
||||
Submitting 12 tasks, each one waiting for 200ms.
|
||||
Immediately after submission, should have: 12 tasks total, 0 tasks running, 12 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 12 tasks total, 0 tasks running, 12 tasks queued -> PASSED!
|
||||
300ms later, should still have: 12 tasks total, 0 tasks running, 12 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 12 tasks total, 0 tasks running, 12 tasks queued -> PASSED!
|
||||
Unpausing pool.
|
||||
Task 1 done.
|
||||
Task 2 done.
|
||||
Task 3 done.
|
||||
Task 0 done.
|
||||
Task 2 done.
|
||||
300ms later, should have: 8 tasks total, 4 tasks running, 4 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 8 tasks total, 4 tasks running, 4 tasks queued -> PASSED!
|
||||
Pausing pool and using wait_for_tasks() to wait for the running tasks.
|
||||
Task 6 done.
|
||||
Task 4 done.
|
||||
Task 5 done.
|
||||
Task 6 done.
|
||||
Task 7 done.
|
||||
Task 4 done.
|
||||
After waiting, should have: 4 tasks total, 0 tasks running, 4 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 4 tasks total, 0 tasks running, 4 tasks queued -> PASSED!
|
||||
200ms later, should still have: 4 tasks total, 0 tasks running, 4 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 4 tasks total, 0 tasks running, 4 tasks queued -> PASSED!
|
||||
Unpausing pool and using wait_for_tasks() to wait for all tasks.
|
||||
Task 9 done.
|
||||
Task 10 done.
|
||||
Task 11 done.
|
||||
Task 8 done.
|
||||
Task 10 done.
|
||||
After waiting, should have: 0 tasks total, 0 tasks running, 0 tasks queued...
|
||||
-> PASSED!
|
||||
Result: 0 tasks total, 0 tasks running, 0 tasks queued -> PASSED!
|
||||
Resetting pool to 24 threads.
|
||||
|
||||
=======================================
|
||||
Checking that exception handling works:
|
||||
=======================================
|
||||
Throwing exception...
|
||||
-> PASSED!
|
||||
|
||||
============================================================
|
||||
Testing that vector operations produce the expected results:
|
||||
============================================================
|
||||
Adding two vectors with 83788 elements using 24 tasks...
|
||||
Adding two vectors with 817976 elements using 4 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 595750 elements using 3 tasks...
|
||||
Adding two vectors with 525623 elements using 23 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 738336 elements using 20 tasks...
|
||||
Adding two vectors with 75250 elements using 7 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 100123 elements using 24 tasks...
|
||||
Adding two vectors with 255236 elements using 24 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 921883 elements using 24 tasks...
|
||||
Adding two vectors with 791117 elements using 3 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 76713 elements using 22 tasks...
|
||||
Adding two vectors with 568990 elements using 7 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 891037 elements using 2 tasks...
|
||||
Adding two vectors with 799419 elements using 23 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 245369 elements using 17 tasks...
|
||||
Adding two vectors with 460301 elements using 13 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 39624 elements using 11 tasks...
|
||||
Adding two vectors with 508493 elements using 19 tasks...
|
||||
-> PASSED!
|
||||
Adding two vectors with 295307 elements using 10 tasks...
|
||||
Adding two vectors with 196258 elements using 2 tasks...
|
||||
-> PASSED!
|
||||
|
||||
++++++++++++++++++++++++++++++
|
||||
@@ -1071,17 +1083,23 @@ Thread pool performance test completed!
|
||||
|
||||
This CPU has 12 physical cores, with each core providing two separate logical cores via hyperthreading, for a total of 24 threads. Without hyperthreading, we would expect a maximum theoretical speedup of 12x. With hyperthreading, one might naively expect to achieve up to a 24x speedup, but this is in fact impossible, as both logical cores share the same physical core's resources. However, generally we would expect [an estimated 30% additional speedup](https://software.intel.com/content/www/us/en/develop/articles/how-to-determine-the-effectiveness-of-hyper-threading-technology-with-an-application.html) from hyperthreading, which amounts to around 15.6x in this case. In our performance test, we see a speedup of 18.2x, saturating and even surpassing this estimated theoretical upper bound.
|
||||
|
||||
## Issue and pull request policy
|
||||
## About the project
|
||||
|
||||
### Issue and pull request policy
|
||||
|
||||
This package is under continuous and active development. If you encounter any bugs, or if you would like to request any additional features, please feel free to [open a new issue on GitHub](https://github.com/bshoshany/thread-pool/issues) and I will look into it as soon as I can.
|
||||
|
||||
Contributions are always welcome. However, I release my projects in cumulative updates after editing and testing them locally on my system, so my policy is not to accept any pull requests. If you open a pull request, and I decide to incorporate your suggestion into the project, I will first modify your code to comply with the project's coding conventions (formatting, syntax, naming, comments, programming practices, etc.), and perform some tests to ensure that the change doesn't break anything. I will then merge it into the next release of the project, possibly together with some other changes. The new release will also include a note in `CHANGELOG.md` with a link to your pull request, and modifications to the documentation in `README.md` as needed.
|
||||
|
||||
## Acknowledgements
|
||||
### Acknowledgements
|
||||
|
||||
Many GitHub users have helped improve this project, directly or indirectly, via issues, pull requests, comments, and/or personal correspondence. Please see `CHANGELOG.md` for links to specific issues and pull requests that have been the most helpful. Thank you all for your contribution! :)
|
||||
|
||||
## Copyright and citing
|
||||
### Starring the repository
|
||||
|
||||
If you found this project useful, please consider [starring it on GitHub](https://github.com/bshoshany/thread-pool/stargazers)! This allows me to see how many people are using my code, and motivates me to keep working to improve it.
|
||||
|
||||
### Copyright and citing
|
||||
|
||||
Copyright (c) 2022 [Barak Shoshany](http://baraksh.com). Licensed under the [MIT license](LICENSE.txt).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user