40#include <gtest/gtest.h>
51using namespace std::chrono_literals;
97 auto future = pool.
enqueue([](
int a,
int b) {
return a + b; }, 10, 20);
115 std::atomic<bool> executed{
false};
117 auto future = pool.
enqueue([&executed] { executed =
true; });
127 std::vector<std::future<int>>
futures;
139 auto token = source.
token();
151 std::atomic<int>
sum{0};
153 for (
int i = 1; i <= 8; ++i)
154 group.launch([&
sum, i] { sum.fetch_add(i, std::memory_order_relaxed); });
172 throw std::logic_error(
"boom");
208 std::atomic<int>
sum{0};
211 [&] {
sum.fetch_add(1, std::memory_order_relaxed); },
212 [&] {
sum.fetch_add(10, std::memory_order_relaxed); },
213 [&] {
sum.fetch_add(100, std::memory_order_relaxed); });
225 [] {
throw std::runtime_error(
"parallel_invoke boom"); },
249 std::vector<int>
input(1024);
263 std::vector<int>
input = {3, 1, 4, 1, 5, 9};
276 std::vector<int>
input = {1, 2, 3, 4, 5};
286 std::vector<int>
input(2048, 1);
303 std::vector<int> left(500), right(700), merged(1200),
expected(1200);
304 std::iota(left.begin(), left.end(), 0);
305 std::iota(right.begin(), right.end(), 250);
307 std::merge(left.begin(), left.end(), right.begin(), right.end(),
expected.begin());
308 pmerge(pool, left.begin(), left.end(), right.begin(), right.end(), merged.begin());
316 std::vector<int> left = {9, 7, 5, 3, 1};
317 std::vector<int> right = {10, 8, 6, 4, 2};
318 std::vector<int> merged(left.size() + right.size());
323 pmerge(left.begin(), left.end(), right.begin(), right.end(),
324 merged.begin(), std::greater<int>{},
options);
326 EXPECT_EQ(merged, (std::vector<int>{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}));
332 std::vector<int> left(1024), right(1024), merged(2048);
333 std::iota(left.begin(), left.end(), 0);
334 std::iota(right.begin(), right.end(), 0);
343 merged.begin(), std::less<int>{},
options),
367 std::vector<int>
input = {42};
368 std::vector<int>
output(1);
395 std::vector<int>
input = {7};
396 std::vector<int>
output(1);
410 std::vector<int> left, right = {1, 2, 3};
411 std::vector<int> merged(right.size());
412 pmerge(pool, left.begin(), left.end(), right.begin(), right.end(), merged.begin());
417 std::fill(merged.begin(), merged.end(), 0);
418 pmerge(left.begin(), left.end(), right.begin(), right.end(),
419 merged.begin(), std::less<int>{},
options);
426 std::vector<int> left = {1, 2, 3}, right;
427 std::vector<int> merged(left.size());
428 pmerge(pool, left.begin(), left.end(), right.begin(), right.end(), merged.begin());
433 std::fill(merged.begin(), merged.end(), 0);
434 pmerge(left.begin(), left.end(), right.begin(), right.end(),
435 merged.begin(), std::less<int>{},
options);
442 std::vector<int> left, right, merged;
443 pmerge(pool, left.begin(), left.end(), right.begin(), right.end(), merged.begin());
448 pmerge(left.begin(), left.end(), right.begin(), right.end(),
449 merged.begin(), std::less<int>{},
options);
460 auto scan_start = std::chrono::high_resolution_clock::now();
462 auto scan_end = std::chrono::high_resolution_clock::now();
464 std::vector<int> left(100000), right(100000), merged(200000);
465 std::iota(left.begin(), left.end(), 0);
466 std::iota(right.begin(), right.end(), 50000);
468 auto merge_start = std::chrono::high_resolution_clock::now();
469 pmerge(pool, left.begin(), left.end(), right.begin(), right.end(), merged.begin());
470 auto merge_end = std::chrono::high_resolution_clock::now();
472 auto scan_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
474 auto merge_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
477 EXPECT_TRUE(std::is_sorted(merged.begin(), merged.end()));
481 std::cout <<
"\n=== Benchmark: pscan / pmerge ===\n";
482 std::cout <<
"pscan(200K): " <<
scan_ms <<
" ms\n";
483 std::cout <<
"pmerge(200K): " <<
merge_ms <<
" ms\n";
484 std::cout <<
"=================================\n\n";
494 std::atomic<bool> block{
true};
497 pool.
enqueue_detached([&block] {
while (block) std::this_thread::yield(); });
500 for (
int i = 0; i < 5; ++i)
517 auto f1 = pool.
enqueue([] {
return 1; });
518 auto f2 = pool.
enqueue([] {
return 2; });
536 for (
int i = 0; i < 10; ++i)
608 std::atomic<bool> block{
true};
612 pool.
enqueue_detached([&block] {
while (block) std::this_thread::yield(); });
615 for (
int i = 0; i < 5; ++i)
637 throw std::runtime_error(
"test exception");
649 throw std::runtime_error(
"test");
653 auto f2 = pool.
enqueue([] {
return 42; });
693 std::vector<std::future<void>>
futures;
694 for (
int i = 0; i < 100; ++i)
697 int current = ++concurrent_count;
700 int prev_max = max_concurrent.load();
701 while (current > prev_max &&
702 !max_concurrent.compare_exchange_weak(prev_max, current))
705 std::this_thread::sleep_for(1ms);
723 ThreadPool pool(std::thread::hardware_concurrency());
725 std::atomic<int>
sum{0};
727 auto start = std::chrono::high_resolution_clock::now();
729 std::vector<std::future<void>>
futures;
738 auto end = std::chrono::high_resolution_clock::now();
739 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
751 ThreadPool pool(std::thread::hardware_concurrency());
755 auto compute = [](
int n) {
757 for (
int i = 0; i < n; ++i)
758 sum += std::sqrt(
static_cast<double>(i));
762 std::vector<std::future<double>>
futures;
770 const auto start = std::chrono::steady_clock::now();
772 std::chrono::steady_clock::now() - start < 1s)
774 std::this_thread::sleep_for(1
ms);
788 auto future = pool.
enqueue([] {
return std::string(
"hello"); });
798 return std::vector<int>{1, 2, 3, 4, 5};
801 auto result =
future.get();
811 return std::make_pair(a + b, a * b);
853 std::function<
int(
int)> func = [](
int x) {
return x * x; };
868 int add(
int x) {
return value + x; }
870 static int square(
int x) {
return x * x; }
923 auto ptr = std::make_unique<int>(42);
924 auto future = pool.
enqueue([p = std::move(ptr)]() {
return *p; });
933 auto ptr = std::make_unique<int>(100);
934 auto future = pool.
enqueue([](std::unique_ptr<int> p) {
return *p * 2; },
946 std::unique_ptr<int> data;
953 int operator()() {
return *data; }
970 for (
int i = 0; i < 10; ++i)
981 std::atomic<int>
sum{0};
983 for (
int i = 1; i <= 5; ++i)
1020 std::vector<int>
inputs = {1, 2, 3, 4, 5};
1035 std::vector<std::string>
inputs = {
"hello",
"world",
"test"};
1050 std::vector<int> empty;
1116 const int value = 77;
1118 auto future = pool.
enqueue([](
const int& x) {
return x * 2; }, std::cref(value));
1128 auto future = pool.
enqueue([&value](
int x) { value = x; }, 42);
1154 for (
int i = 0; i < 10; ++i)
1156 std::this_thread::sleep_for(10ms);
1170 auto start = std::chrono::high_resolution_clock::now();
1172 auto end = std::chrono::high_resolution_clock::now();
1174 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
1184 ThreadPool pool(std::thread::hardware_concurrency());
1227 for (
int i = 0; i < 100; ++i)
1231 std::this_thread::sleep_for(1
ms);
1253 for (
int i = 0; i < 10; ++i)
1256 std::lock_guard<std::mutex> lock(mtx);
1270 for (
int i = 0; i < 1000; ++i)
1283 std::vector<int>
large(10000);
1288 auto result =
future.get();
1299 return pool.
enqueue([] {
return 42; });
1320 std::this_thread::sleep_for(100
ms);
1330 auto f1 = pool.
enqueue([]() ->
int {
throw std::runtime_error(
"runtime"); });
1331 auto f2 = pool.
enqueue([]() ->
int {
throw std::logic_error(
"logic"); });
1332 auto f3 = pool.
enqueue([]() ->
int {
throw std::out_of_range(
"range"); });
1333 auto f4 = pool.
enqueue([] {
return 42; });
1346 std::vector<std::future<int>>
futures;
1347 for (
int i = 0; i < 100; ++i)
1350 throw std::runtime_error(
"test");
1360 catch (
const std::runtime_error&)
1381 for (
int i = 0; i < 4; ++i)
1386 std::this_thread::sleep_for(1
ms);
1393 std::this_thread::sleep_for(1
ms);
1410 for (
int i = 0; i < 10; ++i)
1413 std::this_thread::sleep_for(10
ms);
1441 catch (
const std::runtime_error&)
1446 std::this_thread::sleep_for(1
ms);
1451 std::this_thread::sleep_for(20
ms);
1455 std::this_thread::sleep_for(20
ms);
1459 std::this_thread::sleep_for(20
ms);
1476 std::vector<std::future<int>>
futures;
1491 std::atomic<long long>
sum{0};
1514 std::lock_guard<std::mutex> lock(mtx);
1544 auto start_single = std::chrono::high_resolution_clock::now();
1548 auto end_single = std::chrono::high_resolution_clock::now();
1552 size_t num_threads = std::min(8u, std::thread::hardware_concurrency());
1558 auto end_parallel = std::chrono::high_resolution_clock::now();
1571 auto start = std::chrono::high_resolution_clock::now();
1573 std::vector<std::future<int>>
futures;
1581 auto end = std::chrono::high_resolution_clock::now();
1582 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
1596 auto outer = [](
int x) {
1614 auto add = [](
int a,
int b,
int c) {
return a + b + c; };
1615 auto bound = std::bind(add, 10, std::placeholders::_1, 20);
1627 auto generic_add = [](
auto a,
auto b) {
return a + b; };
1643 return std::accumulate(data.begin(), data.end(), 0);
1657 auto sum_all = [](
int a,
int b,
int c,
int d,
int e,
1658 int f,
int g,
int h,
int i,
int j) {
1659 return a + b + c + d + e + f + g +
h + i + j;
1662 auto future = pool.
enqueue(
sum_all, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
1675 std::mutex queue_mutex;
1676 std::condition_variable cv;
1677 std::atomic<bool>
done{
false};
1682 for (
int i = 0; i < 100; ++i)
1685 std::lock_guard<std::mutex> lock(queue_mutex);
1689 std::this_thread::sleep_for(1
ms);
1696 for (
int c = 0; c < 3; ++c)
1702 std::unique_lock<std::mutex> lock(queue_mutex);
1752 std::vector<std::future<int>>
futures;
1753 for (
int i = 0; i < 50; ++i)
1756 for (
int i = 0; i < 50; ++i)
1771 std::this_thread::sleep_for(1
ms);
1775 for (
int i = 0; i < 5; ++i)
1790 std::this_thread::sleep_for(50
ms);
1813 std::this_thread::sleep_for(1
ms);
1817 for (
int i = 0; i < 15; ++i)
1836 std::this_thread::sleep_for(1
ms);
1840 for (
int i = 0; i < 10; ++i)
1846 FAIL() <<
"Expected queue_overflow_error";
1852 EXPECT_NE(std::string(e.what()).find(
"overflow"), std::string::npos);
1867 std::this_thread::sleep_for(1
ms);
1871 for (
int i = 0; i < 3; ++i)
1875 std::atomic<bool>
done{
false};
1876 std::thread t([&pool, &
done] {
1881 std::this_thread::sleep_for(30
ms);
1912 std::this_thread::sleep_for(5
ms);
1934 std::this_thread::sleep_for(200
ms);
1966 std::this_thread::sleep_for(1
ms);
1986 std::vector<std::future<int>>
futures;
1987 for (
int i = 0; i < 100; ++i)
1990 for (
int i = 0; i < 100; ++i)
2000 throw std::runtime_error(
"test");
2014 for (
int i = 0; i < 100; ++i)
2057 std::this_thread::sleep_for(100us);
2113 auto result = pool.
try_enqueue([] {
return 42; });
2127 std::this_thread::sleep_for(1
ms);
2131 for (
int i = 0; i < 5; ++i)
2163 std::this_thread::sleep_for(1
ms);
2167 for (
int i = 0; i < 3; ++i)
2192 auto result = pool.
try_enqueue([](
int a,
int b) {
return a + b; }, 20, 22);
2206 std::this_thread::sleep_for(1
ms);
2214 auto start = std::chrono::high_resolution_clock::now();
2215 for (
int i = 0; i < 1000; ++i)
2217 auto end = std::chrono::high_resolution_clock::now();
2219 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
2238 auto start = std::chrono::high_resolution_clock::now();
2239 while (std::chrono::high_resolution_clock::now() - start <
work_duration)
2245 auto seq_start = std::chrono::high_resolution_clock::now();
2249 auto seq_end = std::chrono::high_resolution_clock::now();
2253 size_t num_threads = std::thread::hardware_concurrency();
2256 auto pool_start = std::chrono::high_resolution_clock::now();
2257 std::vector<std::future<int>>
futures;
2265 auto pool_end = std::chrono::high_resolution_clock::now();
2272 std::cout <<
"\n=== Benchmark: ThreadPool vs Sequential ===\n";
2273 std::cout <<
"Tasks: " <<
num_tasks <<
", Work per task: 100μs\n";
2274 std::cout <<
"Threads: " << num_threads <<
"\n";
2275 std::cout <<
"Sequential: " <<
seq_duration.count() <<
" ms\n";
2276 std::cout <<
"ThreadPool: " <<
pool_duration.count() <<
" ms\n";
2277 std::cout <<
"Speedup: " << std::fixed << std::setprecision(2) <<
speedup <<
"x\n";
2278 std::cout <<
"============================================\n\n";
2281 if (num_threads > 1)
2291 auto start = std::chrono::high_resolution_clock::now();
2295 auto end = std::chrono::high_resolution_clock::now();
2297 auto total_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
2300 std::cout <<
"\n=== Benchmark: Enqueue Overhead ===\n";
2301 std::cout <<
"Tasks: " <<
num_tasks <<
"\n";
2302 std::cout <<
"Total time: " <<
total_ns / 1000000.0 <<
" ms\n";
2303 std::cout <<
"Per task: " << std::fixed << std::setprecision(0) <<
ns_per_task <<
" ns\n";
2304 std::cout <<
"Throughput: " << std::fixed << std::setprecision(0)
2306 std::cout <<
"===================================\n\n";
2317 auto compute = [](
int x) ->
long long {
2319 for (
int i = 0; i < 1000; ++i)
2320 sum +=
static_cast<long long>(i) * x;
2325 auto async_start = std::chrono::high_resolution_clock::now();
2329 async_futures.push_back(std::async(std::launch::async, compute, i));
2334 auto async_end = std::chrono::high_resolution_clock::now();
2338 ThreadPool pool(std::thread::hardware_concurrency());
2340 auto pool_start = std::chrono::high_resolution_clock::now();
2349 auto pool_end = std::chrono::high_resolution_clock::now();
2356 std::cout <<
"\n=== Benchmark: ThreadPool vs std::async ===\n";
2357 std::cout <<
"Tasks: " <<
num_tasks <<
"\n";
2358 std::cout <<
"std::async: " <<
async_duration.count() / 1000.0 <<
" ms\n";
2359 std::cout <<
"ThreadPool: " <<
pool_duration.count() / 1000.0 <<
" ms\n";
2360 std::cout <<
"Speedup: " << std::fixed << std::setprecision(2) <<
speedup <<
"x\n";
2361 std::cout <<
"============================================\n\n";
2380 EXPECT_EQ(stats.current_queue_size, 0u);
2391 std::vector<std::future<int>>
futures;
2392 for (
int i = 0; i < 10; ++i)
2402 EXPECT_EQ(stats.total_processed(), 10u);
2414 for (
int i = 0; i < 5; ++i)
2417 std::this_thread::sleep_for(std::chrono::milliseconds(10));
2431 pool.
enqueue([] {
return 1; }).get();
2457 std::rethrow_exception(
ep);
2458 }
catch (
const std::runtime_error& e) {
2466 throw std::runtime_error(
"test error");
2470 std::this_thread::sleep_for(std::chrono::milliseconds(50));
2488 std::this_thread::sleep_for(std::chrono::milliseconds(20));
2497 std::this_thread::sleep_for(std::chrono::milliseconds(20));
2512 std::this_thread::sleep_for(std::chrono::milliseconds(20));
2526 pool.
enqueue([] {
return 42; }).get();
2528 bool result = pool.
wait_all_for(std::chrono::milliseconds(100));
2538 std::this_thread::sleep_for(std::chrono::milliseconds(500));
2541 std::this_thread::sleep_for(std::chrono::milliseconds(10));
2543 bool result = pool.
wait_all_for(std::chrono::milliseconds(50));
2554 pool.
enqueue([] {
return 1; }).get();
2556 auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
2567 std::vector<std::tuple<int, int>>
args = {
2568 {1, 2}, {3, 4}, {5, 6}, {7, 8}
2586 std::vector<std::tuple<int>>
args;
2587 for (
int i = 0; i < 100; ++i)
2588 args.emplace_back(i);
2604 std::vector<std::tuple<int>> empty;
2616 std::vector<int> data(100, 1);
2618 parallel_for(pool, data.begin(), data.end(), [](
int& x) { x *= 2; });
2628 std::vector<int> data(100, 1);
2631 [](
auto begin,
auto end) {
2632 for (auto it = begin; it != end; ++it)
2644 std::vector<int> empty;
2647 parallel_for(pool, empty.begin(), empty.end(), [](
int& x) { x = 0; });
2656 std::vector<std::atomic<int>> data(50);
2657 for (
auto& x : data)
2661 [](std::atomic<int>& x) { ++x; },
2664 for (
const auto& x : data)
2674 std::vector<int> data(100, 0);
2677 data[i] = static_cast<int>(i * 2);
2680 for (
size_t i = 0; i < data.size(); ++i)
2681 EXPECT_EQ(data[i],
static_cast<int>(i * 2));
2699 std::vector<int>
input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
2700 std::vector<int>
output(10);
2703 [](
int x) { return x * x; });
2705 EXPECT_EQ(
output, (std::vector<int>{1, 4, 9, 16, 25, 36, 49, 64, 81, 100}));
2712 std::vector<int>
input(1000);
2715 std::vector<int>
output(1000);
2718 [](
int x) { return x + 1; });
2720 for (
int i = 0; i < 1000; ++i)
2728 std::vector<int>
input;
2732 output.begin(), [](
int x) { return x; });
2743 std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
2754 std::vector<int> data = {1, 2, 3, 4, 5};
2757 std::multiplies<int>());
2766 std::vector<int> data = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
2769 std::numeric_limits<int>::min(),
2770 [](
int a,
int b) { return std::max(a, b); });
2779 std::vector<long long> data(10000);
2780 std::iota(data.begin(), data.end(), 1LL);
2783 std::plus<long long>());
2793 std::vector<int> empty;
2816 ::testing::InitGoogleTest(&
argc,
argv);
Cooperative cancellation source paired with CancellationToken.
CancellationToken token() const noexcept
Return a token observing this source.
void request_cancel() noexcept
Request cancellation for all derived tokens.
Minimal structured-concurrency helper over ThreadPool futures.
A reusable thread pool for efficient parallel task execution.
bool try_enqueue_detached(F &&f, Args &&... args)
Try to submit a detached task without blocking or throwing.
size_t num_threads() const noexcept
Get the number of worker threads.
bool wait_all_for(std::chrono::duration< Rep, Period > timeout)
Wait until all current tasks complete, with timeout.
void enqueue_detached(F &&f, Args &&... args)
Submit a task without tracking the result (fire-and-forget).
void set_exception_callback(ExceptionCallback callback)
Set callback for exceptions in detached tasks.
auto enqueue_batch(F &&f, const Container &args_list) -> std::vector< std::future< decltype(std::apply(f, *std::begin(args_list)))> >
Submit multiple tasks with a single notification.
bool is_idle() const
Check if the pool is idle (no pending or running tasks).
auto enqueue_bounded(F &&f, Args &&... args) -> std::future< std::invoke_result_t< F, Args... > >
Submit a task with backpressure and memory protection.
auto enqueue_bulk(F &&f, const Container &args_container) -> std::vector< std::future< std::invoke_result_t< F, typename Container::value_type > > >
Submit multiple tasks and collect all futures.
void resize(size_t new_size)
Resize the pool to a different number of workers.
ThreadPoolStats get_stats() const
Get current pool statistics.
void reset_stats()
Reset statistics counters to zero.
bool wait_all_until(std::chrono::time_point< Clock, Duration > deadline)
Wait until all current tasks complete, with deadline.
bool is_stopped() const noexcept
Check if the pool has been shut down.
void wait_all(const std::chrono::milliseconds poll_interval=std::chrono::milliseconds(1))
Wait until all current tasks complete.
std::pair< size_t, size_t > get_queue_limits() const
Get current queue limits.
auto enqueue(F &&f, Args &&... args) -> std::future< std::invoke_result_t< F, Args... > >
Submit a task for execution and get a future for the result.
void enqueue_bounded_detached(F &&f, Args &&... args)
Submit a task with backpressure, without tracking result.
size_t pending_tasks() const
Get the number of pending tasks in the queue.
void set_queue_limits(const size_t soft_limit, const size_t hard_limit=std::numeric_limits< size_t >::max())
Set queue limits for bounded enqueue operations.
void shutdown()
Shut down the pool, completing all pending tasks.
auto try_enqueue(F &&f, Args &&... args) -> std::optional< std::future< std::invoke_result_t< F, Args... > > >
Try to submit a task without blocking or throwing.
Exception thrown when cooperative cancellation is observed.
Exception thrown when the task queue exceeds its hard limit.
size_t current_size() const noexcept
Current queue size when exception was thrown.
size_t hard_limit() const noexcept
Hard limit that was exceeded.
Main namespace for Aleph-w library functions.
T parallel_reduce(ThreadPool &pool, Iterator first, Iterator last, T init, BinaryOp op, size_t chunk_size=0)
Reduce elements in parallel.
OutputIt parallel_transform(ThreadPool &pool, InputIt first, InputIt last, OutputIt d_first, F &&f, size_t chunk_size=0)
Transform elements in parallel and store results.
ThreadPool & default_pool()
Return the default shared thread pool instance.
bool completed() const noexcept
Return true if all underlying iterators are finished.
auto pexclusive_scan(ThreadPool &pool, const Container &c, T init, BinaryOp op, size_t chunk_size=0)
Parallel exclusive scan over a container.
void parallel_for_index(ThreadPool &pool, size_t start, size_t end, F &&f, size_t chunk_size=0)
Apply a function to each element in parallel (index-based).
void parallel_for(ThreadPool &pool, Iterator begin, Iterator end, F &&f, size_t chunk_size=0)
Execute a function in parallel over a range.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
auto pmerge(ThreadPool &pool, const Container1 &c1, const Container2 &c2, Compare comp=Compare{}, size_t chunk_size=0)
Parallel merge of two sorted containers.
T product(const Container &container, const T &init=T{1})
Compute product of all elements.
auto pscan(ThreadPool &pool, const Container &c, BinaryOp op, size_t chunk_size=0)
Parallel inclusive scan over a container.
void parallel_invoke(ThreadPool &pool, Fs &&... fs)
Invoke a small set of related callables in parallel.
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
Basic arithmetic operations for the calculator.
double add(double a, double b)
static struct argp_option options[]
Common configuration object for parallel algorithms.
ThreadPool * pool
Executor to use (nullptr = default_pool()).
Statistics collected by ThreadPool.
double queue_utilization(size_t soft_limit) const noexcept
Queue utilization as percentage (0-100)
size_t current_queue_size
Current pending tasks.
int multiply(int a, int b) const
int operator()(int x) const
A modern, efficient thread pool for parallel task execution.
void increment_ref(int &x)
TEST_F(ThreadPoolTest, DefaultConstruction)
void add_to_ref(int &x, int amount)