Skip to content

Batch Sensor Frames

Goal

Submit a group of similar independent tasks at once, choosing a future-owning or fire-and-forget path based on whether each completion must be observed.

cpp
#include <atomic>
#include <functional>
#include <iostream>
#include <vector>

#include <executor/executor.hpp>

int main() {
    executor::Executor executor;
    executor::ExecutorConfig config;
    config.min_threads = 2;
    config.max_threads = 2;
    if (!executor.initialize_ex(config)) {
        return 1;
    }

    std::atomic<int> processed{0};
    std::vector<std::function<void()>> tasks;
    for (int index = 0; index < 3; ++index) {
        tasks.push_back([&] { ++processed; });
    }

    auto futures = executor.submit_batch(tasks);
    for (auto& future : futures) {
        future.get();
    }

    executor.submit_batch_no_future(tasks);
    const bool completed = executor.wait_for_completion_for(std::chrono::seconds(1));
    std::cout << "batch processed=" << processed.load()
              << ", completed=" << (completed ? "yes" : "no") << '\n';

    executor.shutdown();
    return completed && processed == 6 ? 0 : 1;
}
bash
./build/examples/tutorial/tutorial_04_batch
text
batch processed=6, completed=yes
NeedAPIFailure observation
Each item needs completion or exceptionsubmit_batch()Call get() on every future.
Per-item results are unnecessarysubmit_batch_no_future()Use failure callback/status plus bounded waiting or shutdown semantics.
The entire group is urgentsubmit_batch_priority(priority, tasks)Inspect every future as with submit_batch().

Bind every task's inputs

Batch APIs take independently callable, already-bound void() tasks, usually a std::vector<std::function<void()>>; they do not provide submit_batch(fn, args...).

cpp
std::vector<std::function<void()>> tasks;
for (SensorFrame frame : frames) {
    tasks.push_back([frame, processor] { processor->process(frame); });
}
auto futures = executor.submit_batch(tasks);

Each closure owns a frame copy and shares processor lifetime. Never capture a loop variable as [&frame]: a later iteration or departed scope can leave tasks with the same object or a dangling reference. For large data, use a buffer handle with a defined return protocol or shared_ptr<const FrameData>, not an unproven view.

The list and its callables must currently be copyable. Use a shared owner for move-only resources, or submit items individually with move-capture lambdas. Batch futures report completion of each void() callable; they do not collect business return values.

Boundaries to preserve

Batch tasks must be independent, produced together, and have the same scheduling meaning. Future positions match input positions, but completion order is unspecified. A failed task does not roll back completed siblings; a batch is not a business transaction.

Test an exception in the middle of a batch, an empty callable, submission after shutdown, failure in a no-future task, and work exceeding an exit budget. Consume all futures and keep captured objects alive until all work completes.

For different per-item results, submit individually or provide an indexed result container. For dependencies, use handles and when_all(); for continuously arriving work, use upstream rate limiting, chunking, and capacity budgets rather than unbounded queue growth.

Next: load, sense, then plan.