Concepts
This page explains the main concepts behind vix::threadpool.
The goal of the module is to make concurrent and parallel C++ programming simple for users, while keeping the complex runtime details inside Vix.
Thread pool
A thread pool owns a fixed number of worker threads. Instead of creating a new std::thread for every task, the pool creates workers once, then reuses them:
vix::threadpool::ThreadPool pool(4);
pool.post([]() { /* Runs on a worker thread. */ });The user does not manage thread creation, joining, task queues, worker loops, condition variables, or shutdown logic. Vix handles that internally.
Task
A task is one unit of work submitted to the pool. Internally it stores: a task id, a callable, task options, status, result, priority, sequence number, timing information, and a captured exception.
A user normally writes:
pool.post([]() { /* Work */ });
// or:
auto future = pool.submit([]() { return 42; });Vix wraps the callable into an internal task.
post
Use post() for fire-and-forget work:
std::atomic<int> counter{0};
pool.post([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); });
pool.wait_idle();Use post() when you do not need a return value, do not need get(), and the task can run in the background.
submit
Use submit() when the task returns a result. If the task throws, the exception is stored in the future and rethrown by get():
auto future = pool.submit([]() { return 42; });
int value = future.get();Future
A Future<T> represents a result that will be available later:
vix::threadpool::Future<int> future = pool.submit([]() { return 42; });
future.wait();
int value = future.get();
auto status = future.status();
auto result = future.result();
auto error = future.error();get() consumes the result. Calling get() more than once is an error.
Promise
Promise<T> is the producer side of a future. The threadpool uses promises internally. Conceptually:
Promise<int> promise;
Future<int> future = promise.get_future();
promise.set_value(42);
int value = future.get();Task status and result
TaskStatus describes the lifecycle state. TaskResult describes the final outcome.
Common statuses: created, queued, running, completed, failed, cancelled, timed_out, rejected.
Common results: none, success, failure, cancelled, timeout, rejected.
if (future.result() == vix::threadpool::TaskResult::success)
{
// Task completed successfully.
}Task options
TaskOptions controls how a task is submitted. It can configure priority, timeout, deadline, cancellation, worker affinity, detached mode, and allow-after-stop behavior:
vix::threadpool::TaskOptions options;
options.set_priority(vix::threadpool::TaskPriority::high);
options.set_timeout(vix::threadpool::Timeout::milliseconds(100));
auto future = pool.submit([]() { return 42; }, options);Priority
Priority affects the order in which queued tasks are selected. Levels: lowest, low, normal, high, highest.
Higher priority tasks are selected before lower priority tasks in the same queue. Priority does not interrupt a task that is already running.
Sequence number
The sequence number keeps stable ordering between tasks with the same priority — the task submitted earlier runs first (predictable FIFO within a priority level).
Worker
A worker is one execution unit inside the pool. Each worker owns one worker thread, one local task queue, worker metrics, and a lifecycle state. The pool and scheduler manage workers internally.
Scheduler
The scheduler distributes tasks across workers. Scheduling policies include: round_robin, least_loaded, affinity, affinity_then_least_loaded. The default is affinity_then_least_loaded.
Queue and rejection policies
The queue policy describes task ordering: priority, fifo, lifo. The default is priority-based.
Rejection policies when a task cannot be accepted: reject (default), caller_runs, discard. A task can be rejected when the pool is stopped, the queue is full, the task is invalid, or no worker is available.
Cancellation
Cancellation is cooperative — Vix does not forcibly kill running C++ code.
CancellationSource → requests cancellation
CancellationToken → observes cancellationauto handle = pool.handle([]() { return 42; });
handle.cancel();The handle owns a cancellation source. The submitted task receives a matching cancellation token through its options.
For long-running work, pass the token and check it:
if (token.cancelled()) { return; }Timeout
A timeout describes a maximum expected duration. Timeouts are observational — Vix marks a task as timed out after it finishes if the elapsed time exceeded the timeout. Vix does not stop the task while it is running.
vix::threadpool::TaskOptions options;
options.set_timeout(vix::threadpool::Timeout::milliseconds(50));Deadline
A deadline is an absolute time limit. If the deadline is already expired before a task starts, the task can be skipped and marked as timed out.
auto deadline = vix::threadpool::Deadline::after(std::chrono::milliseconds{100});TaskHandle
TaskHandle<T> combines a task id, future, and cancellation source:
auto handle = pool.handle([]() { return 42; });
handle.cancel();
handle.wait();
int value = handle.get();Useful when the caller needs both a result and cancellation control.
TaskGroup
TaskGroup coordinates several related tasks. It tracks total/pending/completed/failed/cancelled/timed-out/rejected tasks, a first exception, and shared cancellation:
vix::threadpool::TaskGroup group;
group.add_task(vix::threadpool::TaskId{1});
group.add_task(vix::threadpool::TaskId{2});
group.finish_task(vix::threadpool::TaskStatus::completed,
vix::threadpool::TaskResult::success);
group.finish_task(vix::threadpool::TaskStatus::completed,
vix::threadpool::TaskResult::success);
group.wait();Scope
Scope provides structured concurrency. It is bound to a thread pool and tracks tasks spawned inside it:
vix::threadpool::Scope scope(pool);
scope.spawn([]() { /* Work A */ });
scope.spawn([]() { /* Work B */ });
scope.wait();The destructor waits for spawned tasks and swallows exceptions. Use wait_and_rethrow() when exceptions must be observed.
Parallel algorithms
parallel_for — numeric index ranges
vix::threadpool::parallel_for(pool, std::size_t{0}, values.size(),
[&values](std::size_t index) { values[index] = static_cast<int>(index); });parallel_for_each — containers or iterator ranges
vix::threadpool::parallel_for_each(pool, values,
[](int &value) { value *= 2; });parallel_map — transform to new vector
std::vector<int> squares = vix::threadpool::parallel_map(pool, values,
[](int value) { return value * value; });parallel_reduce — reduce to one result
int sum = vix::threadpool::parallel_reduce(pool, values, 0,
[](int current, int value) { return current + value; });parallel_pipeline — independent stages concurrently
vix::threadpool::parallel_pipeline(pool,
[]() { load_data(); },
[]() { warm_cache(); },
[]() { prepare_logs(); });Chunk size
Parallel algorithms split work into chunks. Each chunk is processed by one submitted task. If chunk size is zero, Vix chooses a safe default.
Small chunks: better load balancing, more overhead. Large chunks: less overhead, may balance less well.
vix::threadpool::ParallelForOptions options;
options.chunk_size = 1024;Executor
Executor is a small abstraction for fire-and-forget work, allowing higher-level modules to depend on an execution interface without depending directly on ThreadPool.
InlineExecutor runs tasks immediately on the caller thread — useful for tests. ThreadPoolExecutor adapts a ThreadPool to the Executor interface (non-owning; the pool must outlive the adapter).
PeriodicTask
Submits a callback repeatedly to an executor:
vix::threadpool::PeriodicTaskConfig config;
config.interval = std::chrono::milliseconds{100};
config.run_immediately = true;
vix::threadpool::PeriodicTask task(pool, callback, config);
task.start();
std::this_thread::sleep_for(std::chrono::seconds{1});
task.stop();
task.join();Latch and Barrier
Latch — one-shot synchronization. Waits until a counter reaches zero; once open, stays open:
vix::threadpool::Latch latch(3);
latch.count_down(); // × 3
latch.wait();Barrier — reusable synchronization. Waits until a fixed number of participants arrive, then resets for the next generation:
vix::threadpool::Barrier barrier(4);
barrier.arrive_and_wait();Metrics and stats
ThreadPoolMetrics — current state snapshot: worker_count, pending_tasks, active_tasks, completed_tasks, failed_tasks, timed_out_tasks, rejected_tasks, etc.
ThreadPoolStats — cumulative historical counters: accepted_tasks, total_execution_time, max_execution_time, etc.
const auto metrics = pool.metrics();
const auto stats = pool.stats();Shutdown
Shutdown is safe and idempotent. A pool configured with drain_on_shutdown = true lets workers finish queued tasks before stopping. When drain is disabled, workers may stop before all queued tasks finish.
pool.shutdown();
pool.shutdown(); // safeWhat Vix does not do
The threadpool does not forcibly kill running C++ functions. That means: cancellation is cooperative, timeout is observational, shutdown waits or stops according to configuration, user code must avoid infinite loops, and user code must protect shared data.
Mental model
ThreadPool → owns Scheduler
Scheduler → owns Workers
Worker → owns WorkerThread + TaskQueue
TaskQueue → stores Tasks
Task → runs user callable
Future → returns result
TaskOptions → configures behaviorThe public API stays simple:
pool.post(fn);
pool.submit(fn).get();
parallel_for(pool, first, last, fn);
pool.shutdown();The internal system keeps the structure needed for reliability, metrics, cancellation, scheduling, and integration with the rest of Vix.