Article by Ayman Alheraki on January 23 2026 10:19 AM
This example demonstrates:
Cooperative thread cancellation (std::jthread, std::stop_token)
Thread-safe shared state
Proper synchronization
RAII-based lifetime management
Clean separation of responsibilities
No data races, no leaks, no undefined behavior
Components
TaskQueue — thread-safe queue
WorkerPool — manages worker threads
main() — submits work and controls lifetime
Key Guarantees
No busy waiting
No manual thread joining
Deterministic shutdown
Exception safety
class TaskQueue {public: using Task = std::function<void()>;
void push(Task task) { { std::lock_guard<std::mutex> lock(m_mutex); m_tasks.push(std::move(task)); } m_cv.notify_one(); }
std::optional<Task> pop(std::stop_token stop) { std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, stop, [this] { return !m_tasks.empty(); });
if (stop.stop_requested() || m_tasks.empty()) return std::nullopt;
Task task = std::move(m_tasks.front()); m_tasks.pop(); return task; }
private: std::queue<Task> m_tasks; std::mutex m_mutex; std::condition_variable_any m_cv;};std::jthread
class WorkerPool {public: explicit WorkerPool(std::size_t threadCount) : m_workers(threadCount) { for (auto& worker : m_workers) { worker = std::jthread([this](std::stop_token stop) { workerLoop(stop); }); } }
void submit(TaskQueue::Task task) { m_queue.push(std::move(task)); }
private: void workerLoop(std::stop_token stop) { while (!stop.stop_requested()) { if (auto task = m_queue.pop(stop)) { (*task)(); } } }
TaskQueue m_queue; std::vector<std::jthread> m_workers;};Why this is correct
std::jthread automatically joins on destruction
std::stop_token enables cooperative cancellation
No explicit join()
No detached threads
No shared mutable state without protection
int main() { WorkerPool pool(4);
for (int i = 0; i < 10; ++i) { pool.submit([i] { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "Task " << i << " executed on thread " << std::this_thread::get_id() << '\n'; }); }
std::this_thread::sleep_for(std::chrono::seconds(2)); return 0;}Threads are owned by objects, not by free functions or global state.
Threads, locks, and synchronization are automatically released.
All shared data is protected. No races. No dangling references.
Threads stop cleanly when the pool is destroyed.
std::jthread
std::stop_token
condition_variable_any
No legacy patterns
Raw std::thread without joining
Global mutexes
Busy loops
Atomic misuse
Detached threads
Manual lifetime control
Task processing systems
Server backends
Job systems
Background workers
I/O pipelines