Introdução: por que precisamos de filas de tarefas em C++?
\n\nNa programação moderna, especialmente ao trabalhar com aplicações multithread, surge frequentemente a necessidade de organizar a execução de tarefas na ordem em que chegam. As filas de tarefas (task queues) são um padrão fundamental que permite distribuir eficientemente o trabalho entre threads, processar requisições de servidores web, gerenciar processos em segundo plano e evitar condições de corrida.
\n\nEm C++, a biblioteca padrão fornece o poderoso contêiner std::queue, que é ideal para implementar filas de tarefas. No entanto, na prática, não basta apenas armazenar elementos; é necessário transferi-los com segurança entre threads, tratar erros e gerenciar o ciclo de vida das tarefas. Neste artigo, vamos analisar como criar filas de tarefas em C++ do zero, usando os recursos modernos da linguagem (C++11 e posteriores).
Abordaremos:
\n- \n
- Funcionamento básico com
std::queue\n - Fila de tarefas thread-safe \n
- Implementação de um pool de threads (thread pool) \n
- Filas prioritárias \n
1. Fila de tarefas básica com std::queue
\n\nVamos começar com um exemplo simples. std::queue é um contêiner adaptador que opera no princípio FIFO (First In, First Out). Ele fornece os métodos push(), pop(), front() e empty().
Aqui está como organizar uma fila de tarefas, onde cada tarefa é uma função (ou um objeto chamável):
\n\n#include <iostream>\n#include <queue>\n#include <functional>\n\nint main() {\n // Fila de tarefas, onde cada tarefa é um std::function<void()>\n std::queue<std::function<void()>> taskQueue;\n\n // Adicionando tarefas\n taskQueue.push([]() { std::cout << "Tarefa 1 executada\\n"; });\n taskQueue.push([]() { std::cout << "Tarefa 2 executada\\n"; });\n taskQueue.push([]() { std::cout << "Tarefa 3 executada\\n"; });\n\n // Processando a fila\n while (!taskQueue.empty()) {\n auto task = taskQueue.front();\n task(); // Executa a tarefa\n taskQueue.pop(); // Remove a tarefa executada\n }\n\n return 0;\n}\n\nEste código funciona, mas apenas em modo single-thread. Se várias threads adicionarem ou removerem tarefas simultaneamente, ocorrerá uma condição de corrida. Para multithreading, é necessária sincronização.
\n\n2. Fila de tarefas thread-safe
\n\nPara tornar a fila segura para acesso multithread, usamos um mutex (std::mutex) e uma variável de condição (std::condition_variable). Este é o padrão clássico produtor-consumidor (producer-consumer).
Implementação de uma fila thread-safe:
\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(); // Notifica uma thread em espera\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; // Sinal de parada\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(); // Acorda todas as threads para finalizar\n }\n};\n\nPontos-chave:
\n- \n
std::lock_gu