Filas de tarefas em Go: guia completo para iniciantes

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

Introdução às filas de tarefas em Go

\n

Filas de tarefas (task queues) são um dos padrões-chave no desenvolvimento de aplicações de alta carga. Elas permitem organizar o processamento assíncrono de dados, distribuir a carga e aumentar a tolerância a falhas do sistema. Na linguagem Go, graças aos mecanismos internos de concorrência — goroutines e channels — a implementação de filas torna-se incrivelmente elegante e eficiente.

\n

Neste artigo, vamos explorar como criar filas de tarefas em Go do zero. Você aprenderá o que são goroutines e channels, como organizar um worker pool e processar tarefas em paralelo. O material é voltado para desenvolvedores iniciantes familiarizados com o básico da sintaxe Go.

\n\n

Fundamentos: goroutines e channels

\n

Antes de passar para as filas, é importante entender dois conceitos fundamentais do Go:

\n
    \n
  • Goroutine — um fluxo de execução leve. É iniciado com a palavra-chave go antes da chamada de uma função.
  • \n
  • Channel — um "canal" tipado para transferir dados entre goroutines. Permite sincronizar o trabalho delas.
  • \n
\n

Exemplo simples de goroutine e channel:

\n
package main\n\nimport "fmt"\n\nfunc main() {\n    ch := make(chan string)\n\n    go func() {\n        ch <- "Olá da goroutine!"\n    }()\n\n    msg := <-ch\n    fmt.Println(msg)\n}\n
\n

Neste exemplo, criamos um channel ch, iniciamos uma goroutine anônima que envia uma mensagem para ele, e a goroutine principal recebeu essa mensagem. É exatamente nesse mecanismo que as filas de tarefas são construídas.

\n\n

Implementação de uma fila de tarefas simples

\n

A fila de tarefas mais simples é um channel no qual colocamos tarefas, e uma ou mais goroutines que as processam. Vejamos um exemplo básico:

\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc worker(id int, tasks <-chan int) {\n    for task := range tasks {\n        fmt.Printf("Worker %d iniciou a tarefa %d\\n", id, task)\n        time.Sleep(time.Second) // simulação de trabalho\n        fmt.Printf("Worker %d concluiu a tarefa %d\\n", id, task)\n    }\n}\n\nfunc main() {\n    tasks := make(chan int, 10) // channel com buffer\n\n    // inicia um worker\n    go worker(1, tasks)\n\n    // envia 5 tarefas\n    for i := 1; i <= 5; i++ {\n        tasks <- i\n    }\n\n    close(tasks) // fecha o channel\n    time.Sleep(2 * time.Second) // dá tempo para processamento\n}\n
\n

Aqui, worker é uma função que lê do channel tasks. O channel tem buffer (capacidade 10), então podemos enviar várias tarefas sem bloqueio. Após fechar o channel, o loop for range termina automaticamente.

\n

Observe: usamos <-chan int — um channel somente leitura. Esta é uma boa prática que indica explicitamente que o worker não deve enviar dados para este channel.

\n\n

Channels com buffer e sem buffer

\n

No exemplo acima, usamos um channel com buffer (make(chan int, 10)). A diferença entre os tipos de channels:

\n
    \n
  • Channel sem buffer — o envio é bloqueado até que o receptor leia os dados. Ideal para sincronização.
  • \n
  • Channel com buffer — o envio é bloqueado apenas quando o buffer está cheio. Permite acumular tarefas e reduzir latências.
  • \n
\n

Para filas de tarefas, geralmente usamos channels com buffer, para que o produtor possa enviar uma tarefa rapidamente e continuar o trabalho, sem esperar sua execução.

\n\n

Worker Pool em Go

\n

Um worker é bom, mas vários são melhores. Um worker pool permite processar tarefas em paralelo, utilizando todos os núcleos do processador. Veja como implementar:

\n
package main\n\nimport (\n    "fmt"\n    "sync"\n    "time"\n)\n\nfunc worker(id int, tasks <-chan int, wg *sync.WaitGroup) {\n    defer wg.Done()\n    fo                

Blogs

Book Recommendations