Trabalhando com Filas de Tarefas em Go: para Iniciantes
Filas de tarefas (task queues) são um dos padrões fundamentais no desenvolvimento moderno. Em Go (Golang), esse mecanismo é implementado de forma especialmente elegante graças aos primitivos de concorrência integrados: goroutines e canais. Neste artigo, vamos analisar como criar filas de tarefas em Go, começando com exemplos simples e avançando para técnicas mais sofisticadas.
O que é uma fila de tarefas e para que serve?
Uma fila de tarefas é uma estrutura de dados que funciona com base no princípio FIFO (First In, First Out — "o primeiro a entrar é o primeiro a sair"). Imagine uma fila no caixa: o primeiro cliente é atendido, depois o segundo e assim por diante. Na programação, isso permite:
- Separar o recebimento de tarefas do seu processamento
- Limitar o número de operações executadas simultaneamente
- Distribuir uniformemente a carga entre os processadores
- Processar tarefas em segundo plano sem bloquear o fluxo principal
Go é ideal para implementar filas graças ao seu modelo de concorrência. Ao contrário de muitas linguagens que exigem uma biblioteca externa (como RabbitMQ ou Redis), em Go é possível criar uma fila de tarefas simples com apenas algumas linhas de código.
Criando uma fila de tarefas simples com canais Go
A maneira mais simples de implementar uma fila de tarefas é usar um canal com buffer. Vamos ver um exemplo básico:
package main
import ( "fmt" "time")
func worker(tasks <-chan string, id int) { for task := range tasks { fmt.Printf("Worker %d iniciou a tarefa: %s", id, task) time.Sleep(1 * time.Second) // Simulação de trabalho fmt.Printf("Worker %d concluiu a tarefa: %s", id, task) }}
func main() { tasks := make(chan string, 5) // Canal com buffer para 5 tarefas
// Iniciando 2 workers for i := 1; i <= 2; i++ { go worker(tasks, i) }
// Enviando tarefas para a fila for i := 1; i <= 10; i++ { tasks <- fmt.Sprintf("Tarefa nº%d", i) }
close(tasks) // Fechando o canal após enviar todas as tarefas time.Sleep(2 * time.Second) // Dando tempo para a conclusão}Neste exemplo:
- O canal
tasksatua como a fila - Dois workers (goroutines) escutam constantemente o canal e processam as tarefas
- O buffer de tamanho 5 permite enviar até 5 tarefas sem processamento imediato
Padrão Worker Pool: Processamento Escalável
Worker Pool (pool de workers) é uma evolução da fila simples. Em vez de um número fixo de workers, criamos um pool que pode ser dimensionado dinamicamente. Este padrão é especialmente útil para:
- Processamento de requisições HTTP
- Processamento em lote de dados
- Trabalho com APIs externas (limitação do número de requisições simultâneas)
Aqui está uma implementação mais avançada usando uma estrutura:
package main
import ( "fmt" "sync" "time")
type Task struct { ID int Payload string}
type WorkerPool struct { tasks chan Task results chan string wg sync.WaitGroup workers int}
func NewWorkerPool(workers int) *WorkerPool { return &WorkerPool{ tasks: make(chan Task, 100), results: make(chan string, 100), workers: workers, }}
func (wp *WorkerPool) Start() { for i := 1; i <= wp.workers; i++ { wp.wg.Add(1) go wp.worker(i) }}
func (wp *WorkerPool) worker(id int) { defer wp.wg.Done() for task := range wp.tasks { result := fmt.Sprintf("Worker %d executou a tarefa %d: %s", id, task.ID, task.Payload) time.Sleep(500 * time.Millisecond) // Simulação de trabalho wp.results <- result }}
func (wp *WorkerPool) AddTask(task Task) { wp.tasks <- task}\