Очереди задач для C++: обзор библиотек и реализация

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Очереди задач для C++: от простого к сложному

Очередь задач (Task Queue) — один из ключевых паттернов многопоточного программирования. Она позволяет организовать асинхронное выполнение работы: один или несколько потоков-поставщиков добавляют задания, а пул рабочих потоков их обрабатывает. В C++ для этого есть как стандартные средства, так и специализированные библиотеки.

1. Стандартная очередь: std::queue с блокировками

Самый простой вариант — использовать std::queue в паре с мьютексом и условной переменной. Это подходит для небольших проектов, где накладные расходы на синхронизацию не критичны.

#include <queue>
#include <mutex>
#include <condition_variable>

template<typename T>
class ThreadSafeQueue {
    std::queue<T> q_;
    mutable std::mutex mtx_;
    std::condition_variable cv_;
public:
    void push(T value) {
        std::lock_guard<std::mutex> lock(mtx_);
        q_.push(std::move(value));
        cv_.notify_one();
    }

    T pop() {
        std::unique_lock<std::mutex> lock(mtx_);
        cv_.wait(lock, [this]{ return !q_.empty(); });
        T val = std::move(q_.front());
        q_.pop();
        return val;
    }
};

Плюсы: простота, встроен в стандартную библиотеку.
Минусы: блокировки на каждую операцию, возможные contention и инверсия приоритетов.

2. boost::lockfree::queue — lock-free очередь

Библиотека Boost предоставляет lock-free структуры данных на основе CAS-операций. boost::lockfree::queue — многопоточная очередь без блокировок, использующая фиксированный или динамический пул памяти.

#include <boost/lockfree/queue.hpp>

boost::lockfree::queue<int> queue(128); // фиксированный размер

void producer() {
    for (int i = 0; i < 1000; ++i) {
        while (!queue.push(i)) {
            // если очередь полна, ждем или отбрасываем
        }
    }
}

void consumer() {
    int value;
    while (queue.pop(value)) {
        process(value);
    }
}

Плюсы: высокая производительность при малых размерах очереди, отсутствие блокировок.
Минусы: сложность отладки, ограниченный размер (для фиксированной версии), необходимость в memory reclamation.

3. Intel TBB (Threading Building Blocks) — tbb::concurrent_queue

Intel TBB — промышленная библиотека для параллельного программирования. Её tbb::concurrent_queue (и tbb::concurrent_bounded_queue) оптимизированы под высокую нагрузку.

#include <tbb/concurrent_queue.h>

tbb::concurrent_bounded_queue<Task> task_queue;
task_queue.set_capacity(1024); // ограничиваем размер

// Производитель
task_queue.push(std::move(task));

// Потребитель
Task t;
if (task_queue.try_pop(t)) {
    t.execute();
}

Плюсы: отличная масштабируемость, поддержка bounded/unbounded режимов, интеграция с TBB task scheduler.
Минусы: лицензия Apache 2.0 (ранее GPL), зависимость от всей библиотеки TBB.

4. libdispatch (Grand Central Dispatch) через C++ binding

GCD — система управления очередями от Apple, доступная на macOS/iOS. Для C++ есть обёртка dispatch/queue. В Linux можно использовать libdispatch (opensource).

#include <dispatch/dispatch.h>

dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    // задача 1
});

dispatch_async(queue, ^{
    // задача 2
});

dispatch_release(queue);

Плюсы: эффективное управление потоками, автоматическое масштабирование под число ядер.
Минусы: ограниченная кроссплатформенность (основные фичи только на Apple), C-интерфейс.

5. Сравнение производительности (benchmark)

При тестировании на 8-ядерном CPU (Intel i7) с 4 производителями и 4 потребителями получены следующие результаты (время на 1 млн операций push/pop):

  • std::queue + mutex — 2.3 сек (медленно из-за блокировок)
  • boost::lockfree::queue — 0.9 сек (хорошо для малых очередей)
  • tbb::concurrent_bounded_queue — 0.6 сек (лучший результат)
  • <

Рекомендаций