Очереди задач для C++: полное руководство по выбору и реализации
В современном программировании на C++ эффективное использование многопоточности — залог производительности приложений. Одним из ключевых паттернов является очередь задач (task queue), которая позволяет распределять работу между потоками без ручного управления их жизненным циклом. В этой статье мы разберём лучшие библиотеки для C++, предоставляющие готовые решения, а также покажем, как реализовать очередь задач своими руками.
Что такое очередь задач и зачем она нужна
Очередь задач — это механизм, при котором вы помещаете в очередь «задания» (функции, лямбды или объекты), а пул потоков (thread pool) автоматически их выполняет. Это избавляет от необходимости создавать и завершать потоки для каждой задачи, снижает накладные расходы и упрощает синхронизацию.
Основные преимущества:
- Автоматическое распределение нагрузки между ядрами CPU
- Повторное использование потоков (экономия ресурсов)
- Простота добавления новых задач без блокировок
- Возможность приоритезации и отмены задач
Популярные библиотеки для очередей задач в C++
1. Intel Threading Building Blocks (TBB)
Одна из самых мощных библиотек, разработанная Intel. Предоставляет tbb::parallel_for, tbb::task_group и гибкий планировщик задач. Подходит для высокопроизводительных вычислений.
#include <tbb/task_group.h>
#include <iostream>
int main() {
tbb::task_group tg;
tg.run([] { std::cout << "Задача 1\n"; });
tg.run([] { std::cout << "Задача 2\n"; });
tg.wait(); // дождаться завершения
return 0;
}
2. Boost.Task (устарел, но концепции живы в Boost.Fiber)
Раньше в Boost была библиотека Boost.Task, сейчас её функционал частично перешёл в Boost.Fiber. Позволяет создавать очереди с приоритетами и асинхронными результатами.
// Пример с Boost.Fiber (современная альтернатива)
#include <boost/fiber/all.hpp>
#include <iostream>
int main() {
boost::fibers::fiber f1([]{
std::cout << "Файбер 1\n";
});
f1.join();
return 0;
}
3. PPL (Parallel Patterns Library) — для Windows
Библиотека от Microsoft, встроенная в Visual Studio. Использует concurrency::task и concurrency::parallel_for. Отлично интегрируется с Windows Runtime.
#include <ppltasks.h>
#include <iostream>
int main() {
concurrency::create_task([]{
std::cout << "Задача PPL\n";
}).wait();
return 0;
}
4. QtConcurrent — для проектов на Qt
Если вы используете Qt, QtConcurrent::run и QThreadPool — простой способ организовать очередь задач.
#include <QtConcurrent/QtConcurrentRun>
#include <iostream>
int main() {
QtConcurrent::run([]{
std::cout << "Задача Qt\n";
});
QThread::msleep(100);
return 0;
}
Реализация простой очереди задач на std::thread
Если вы не хотите подключать внешние библиотеки, можно написать свою очередь. Ниже — минимальный рабочий пример на C++17.
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
class TaskQueue {
public:
explicit TaskQueue(size_t threads) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template<class F>
void enqueue(F&& f) {
{
std::lock_guard<std::mutex> lock(mutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}
~TaskQueue() {
{
std::lock_guard<std::mutex> lock(mutex);
stop = true;
}
condition.notify_all();
for (auto& worker : workers) {
worker.join();
}
}
private:
std::vector<