SimplifyC++ Article
Mastering Concurrency in Modern C++ Architecture, Pitfalls, and Correctness by Example
Mastering Concurrency in Modern C++: Architecture, Pitfalls, and Correctness by Example.
Designing a Thread-Safe Task Processing System in Modern C++ (C++20/23)
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
Design Overview
Components
TaskQueue— thread-safe queueWorkerPool— manages worker threadsmain()— submits work and controls lifetime
Key Guarantees
No busy waiting
No manual thread joining
Deterministic shutdown
Exception safety
Thread-Safe Task Queue
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;};Worker Pool Using 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::jthreadautomatically joins on destructionstd::stop_tokenenables cooperative cancellationNo explicit
join()No detached threads
No shared mutable state without protection
Example Usage
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;}Why This Example Represents Best Practice
1. Correct Thread Ownership
Threads are owned by objects, not by free functions or global state.
2. RAII Everywhere
Threads, locks, and synchronization are automatically released.
3. No Undefined Behavior
All shared data is protected. No races. No dangling references.
4. Cooperative Cancellation
Threads stop cleanly when the pool is destroyed.
5. Modern C++20/23 Style
std::jthreadstd::stop_tokencondition_variable_anyNo legacy patterns
Common Mistakes This Example Avoids
Raw
std::threadwithout joiningGlobal mutexes
Busy loops
Atomic misuse
Detached threads
Manual lifetime control
When This Design Is Appropriate
Task processing systems
Server backends
Job systems
Background workers
I/O pipelines
What did you think?
Sign in to react or comment.
Comments
0No comments yet.