Filas de tarefas em C++: implementação, aplicação e otimização

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course
Here is the translated HTML content in Portuguese (Português), keeping all HTML tags and only translating the text content inside them. ```html

Trabalhando com filas de tarefas em C++: do simples ao complexo



No desenvolvimento moderno em C++, raramente se passa sem processamento assíncrono de dados. As filas de tarefas (task queues) são uma das ferramentas-chave para organizar a execução paralela de código. Elas permitem distribuir o trabalho entre threads, evitar bloqueios e utilizar eficientemente os recursos da CPU. Neste artigo, vamos analisar como implementar uma fila de tarefas em C++, explorar o padrão producer-consumer e aprender a construir um thread pool simples.



1. Fila básica baseada em std::queue



A fila de tarefas mais simples é um contêiner no qual podemos adicionar elementos (tarefas) e removê-los na ordem FIFO. Em C++, o std::queue da biblioteca padrão é perfeito para isso. No entanto, ao trabalhar com multithreading, uma fila comum não é segura: o acesso simultâneo de diferentes threads leva a condições de corrida (data races).



Vamos considerar um exemplo básico de uma fila sincronizada usando mutex e variável de condição:



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

class TaskQueue {public: using Task = std::function<void()>;

void push(Task task) { std::lock_guard<std::mutex> lock(mutex_); queue_.push(std::move(task)); cond_var_.notify_one(); }

Task pop() { std::unique_lock<std::mutex> lock(mutex_); cond_var_.wait(lock, [this]() { return !queue_.empty(); }); Task task = std::move(queue_.front()); queue_.pop(); return task; }

bool empty() const { std::lock_guard<std::mutex> lock(mutex_); return queue_.empty(); }

private: std::queue<Task> queue_; mutable std::mutex mutex_; std::condition_variable cond_var_;};


Neste código, push() adiciona uma tarefa e notifica uma thread em espera. pop() bloqueia a thread até que a fila não esteja vazia. Essa abordagem resolve o problema de sincronização, mas não escala para múltiplos consumidores sem modificações.



2. Padrão Producer-Consumer



O problema clássico de producer-consumer (produtor-consumidor) se encaixa perfeitamente em uma fila de tarefas. Uma ou mais threads produtoras geram tarefas, e threads consumidoras as executam. É importante gerenciar corretamente o tamanho da fila para evitar estouro ou inatividade.



Exemplo com vários consumidores:



#include <thread>#include <vector>#include <iostream>

void worker(TaskQueue& queue, int id) { while (true) { auto task = queue.pop(); if (!task) break; // sinal de término std::cout << "Worker " << id << " executing task"; task(); }}

int main() { TaskQueue queue; std::vector<std::thread> workers;

// Iniciamos 4 threads consumidoras for (int i = 0; i < 4; ++i) { workers.emplace_back(worker, std::ref(queue), i); }

// Produtor adiciona tarefas for (int i = 0; i < 10; ++i) { queue.push([i]() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "Task " << i << " done"; }); }

// Sinal de término (tarefa nullptr) for (auto& w : workers) { queue.push(nullptr); }

for (auto& w : workers) { w.join(); } return 0;}


Aqui, cada consumidor recebe uma tarefa, a executa e aguarda a próxima. O sinal de término (nullptr) interrompe o loop. Esse padrão é frequentemente usado em aplicações de servidor, manipuladores de eventos e motores de jogos.



3. Thread Pool — pool de threads para fila de tarefas



Thread pool (pool de threads) é uma camada sobre a fila de tarefas que gerencia ```

Blogs

Book Recommendations