Introduction: Why Do You Need Task Queues in C++?
\n\nIn modern programming, especially when working with multithreaded applications, there is often a need to organize the execution of tasks in the order they arrive. Task queues are a fundamental pattern that allows you to efficiently distribute work between threads, handle web server requests, manage background processes, and avoid race conditions.
\n\nIn C++, the standard library provides a powerful container std::queue, which is perfectly suited for implementing task queues. However, in practice, you need not just to store elements, but to safely transfer them between threads, handle errors, and manage the task lifecycle. In this article, we will look at how to create task queues in C++ from scratch using modern language features (C++11 and newer).
We will cover:
\n- \n
- Basic work with
std::queue\n - A thread-safe task queue \n
- Implementing a thread pool \n
- Priority queues \n
1. Basic Task Queue with std::queue
\n\nLet's start with a simple example. std::queue is a container adapter that operates on the FIFO (First In, First Out) principle. It provides the methods push(), pop(), front(), and empty().
Here is how you can organize a task queue where each task is a function (or callable object):
\n\n#include <iostream>\n#include <queue>\n#include <functional>\n\nint main() {\n // Task queue where each task is a std::function<void()>\n std::queue<std::function<void()>> taskQueue;\n\n // Add tasks\n taskQueue.push([]() { std::cout << "Task 1 completed\\n"; });\n taskQueue.push([]() { std::cout << "Task 2 completed\\n"; });\n taskQueue.push([]() { std::cout << "Task 3 completed\\n"; });\n\n // Process the queue\n while (!taskQueue.empty()) {\n auto task = taskQueue.front();\n task(); // Execute the task\n taskQueue.pop(); // Remove the completed task\n }\n\n return 0;\n}\n\nThis code works, but only in single-threaded mode. If multiple threads add or retrieve tasks simultaneously, a race condition will occur. Synchronization is needed for multithreading.
\n\n2. Thread-Safe Task Queue
\n\nTo make the queue safe for multithreaded access, we use a mutex (std::mutex) and a condition variable (std::condition_variable). This is the classic producer-consumer pattern.
Implementation of a thread-safe queue:
\n\n#include <queue>\n#include <mutex>\n#include <condition_variable>\n#include <functional>\n#include <thread>\n\nclass ThreadSafeQueue {\nprivate:\n std::queue<std::function<void()>> queue_;\n std::mutex mutex_;\n std::condition_variable cond_;\n bool stop_ = false;\n\npublic:\n void push(std::function<void()> task) {\n std::lock_guard<std::mutex> lock(mutex_);\n queue_.push(task);\n cond_.notify_one(); // Notify one waiting thread\n }\n\n std::function<void()> pop() {\n std::unique_lock<std::mutex> lock(mutex_);\n cond_.wait(lock, [this]() { return !queue_.empty() || stop_; });\n if (stop_ && queue_.empty()) {\n return nullptr; // Stop signal\n }\n auto task = queue_.front();\n queue_.pop();\n return task;\n }\n\n void stop() {\n std::lock_guard<std::mutex> lock(mutex_);\n stop_ = true;\n cond_.notify_all(); // Wake all threads to finish\n }\n};\n\nKey points:
\n- \n
std::lock_gu