Aula 36 – Tutorial Golang – Worker Pools
Aula 36 – Tutorial Golang – Worker Pools
Compartilho com vocês conteúdo de qualidade de forma gratuita como parte da missão do Código Fluente.
No entanto, também quero apresentar uma oportunidade imperdível de aprimorar suas habilidades em programação para alcançar um nível avançado.
Conheça agora mesmo o curso Master Full Stack clicando no link abaixo 👇 e confira!
Página principal do blog
Todas as aulas desse curso
Aula 35 Aula 37
Redes Sociais:
Meus links de afiliados:
Hostinger
Digital Ocean
One.com
Melhore seu NETWORKING
https://digitalinnovation.one/
Participe de comunidades de desenvolvedores:
Fiquem a vontade para me adicionar ao linkedin.
E também para me seguir no https://github.com/toticavalcanti.
Código final da aula:
https://github.com/toticavalcanti
Canais do Youtube
Toti
Lofi Music Zone Beats
Backing Track / Play-Along
Código Fluente
Putz!
Vocal Techniques and Exercises
PIX para doações
Aula 36 – Tutorial Golang – Worker Pools
Introdução aos Worker Pools
Um Worker Pool é um padrão de programação concorrente que envolve a criação de um grupo de trabalhadores (goroutines) que executam tarefas em paralelo.
O conceito é fundamental para otimizar a utilização de recursos em sistemas multi-core e para lidar com tarefas demoradas de maneira eficiente.
A importância dos Worker Pools reside na capacidade de realizar várias tarefas simultaneamente, melhorando significativamente o desempenho e a eficiência dos programas.
Eles permitem distribuir o trabalho entre múltiplas goroutines, evitando gargalos e aproveitando ao máximo a capacidade de processamento disponível.
Isso é particularmente útil para tarefas intensivas, como processamento de dados, chamadas de rede ou cálculos complexos.
As vantagens de usar Worker Pools para processamento concorrente são:
- Eficiência: O uso de múltiplas goroutines permite a execução simultânea de tarefas, aproveitando ao máximo os recursos do sistema e reduzindo o tempo total de processamento.
- Paralelismo Controlado: Worker Pools permitem controlar a quantidade de trabalhadores em execução, evitando sobrecarga excessiva de recursos e gerenciando a concorrência de maneira mais previsível.
- Redução de Gargalos: Ao distribuir as tarefas entre vários trabalhadores, os Worker Pools reduzem a probabilidade de gargalos em tarefas intensivas, melhorando a velocidade geral do processamento.
- Reutilização de Goroutines: As goroutines dos trabalhadores são reutilizadas, evitando o custo de criar e encerrar goroutines repetidamente, o que é mais eficiente em termos de recursos.
- Priorização de Tarefas: Tarefas mais importantes ou sensíveis ao tempo podem ser atribuídas a trabalhadores com maior prioridade, garantindo um melhor controle sobre a ordem de execução.
- Gerenciamento de Erros: Os Worker Pools podem incluir lógica de tratamento de erros centralizada, simplificando a gestão de exceções e o monitoramento de problemas em tarefas individuais.
- Economia de Recursos: Ao aproveitar o paralelismo, os Worker Pools permitem um processamento mais rápido, economizando recursos de tempo e energia.
- Escalabilidade: A estrutura de Worker Pool é escalável, o que significa que pode ser adaptada para lidar com um aumento de carga sem comprometer a performance.
Casos de uso reais de Worker Pools em Go
Caso de Uso 1: Processamento de Downloads
Imagine que você tem uma aplicação que precisa fazer o download de várias imagens de diferentes URLs.
Usar um Worker Pool pode melhorar o desempenho, permitindo que vários downloads ocorram simultaneamente.
package main
import (
"fmt"
"time"
)
const numWorkers = 4
func downloadWorker(id int, urls <-chan string, done chan<- bool) {
for url := range urls {
fmt.Printf("Worker %d iniciou o download de %s\n", id, url)
// Simula o download
time.Sleep(time.Second)
fmt.Printf("Worker %d concluiu o download de %s\n", id, url)
}
done <- true
}
func main() {
urls := []string{"url1", "url2", "url3", "url4", "url5"}
jobs := make(chan string, len(urls))
done := make(chan bool, numWorkers)
for i := 1; i <= numWorkers; i++ {
go downloadWorker(i, jobs, done)
}
for _, url := range urls {
jobs <- url
}
close(jobs)
// Aguarda a conclusão de todos os workers
for i := 1; i <= numWorkers; i++ {
<-done
}
close(done)
}
Explicação passo a passo
Exemplo 1: Processamento de Downloads
Este exemplo simula o processamento de downloads de várias URLs usando um Worker Pool.
- Um canal
jobs
é criado para armazenar as URLs que precisam ser baixadas. Um canaldone
é usado para indicar quando cada worker concluiu seu trabalho. - Um loop cria os workers chamando a função
downloadWorker
com um ID do worker, o canal jobs e o canal done. - Para cada URL, ela é enviada para o canal
jobs
. - Os workers (goroutines) leem as URLs do canal
jobs
, simulam o download e enviam uma confirmação para o canaldone
quando terminam. - No final, esperamos pela conclusão de todos os workers lendo do canal
done
.
Caso de Uso 2: Geração de Relatórios
Suponha que você tenha que gerar relatórios complexos que envolvam processamento intenso.
Usar um Worker Pool pode acelerar o processo, dividindo a tarefa em partes que podem ser executadas concorrentemente.
package main
import (
"fmt"
"time"
)
const numWorkers = 2
func generateReportWorker(id int, tasks <-chan int, done chan<- bool) {
for task := range tasks {
fmt.Printf("Worker %d iniciou a geração do relatório %d\n", id, task)
// Simula o processamento
time.Sleep(2 * time.Second)
fmt.Printf("Worker %d concluiu a geração do relatório %d\n", id, task)
}
done <- true
}
func main() {
numTasks := 6
tasks := make(chan int, numTasks)
done := make(chan bool, numWorkers)
for i := 1; i <= numWorkers; i++ {
go generateReportWorker(i, tasks, done)
}
for task := 1; task <= numTasks; task++ {
tasks <- task
}
close(tasks)
// Aguarda a conclusão de todos os workers
for i := 1; i <= numWorkers; i++ {
<-done
}
close(done)
}
Explicação passo a passo
Exemplo 2: Geração de Relatórios
Este exemplo ilustra a geração de relatórios complexos usando um Worker Pool.
- Um canal
tasks
é criado para armazenar as tarefas de geração de relatório. Um canaldone
é usado para indicar quando cada worker concluiu seu trabalho. - Um loop cria os workers chamando a função
generateReportWorker
com um ID de worker, o canal de tasks e o canal done. - As tarefas de geração de relatório são enviadas para o canal
tasks
. - Os workers (goroutines) leem as tarefas do canal
tasks
, simulam o processamento e enviam uma confirmação para o canaldone
quando terminam. - No final, esperamos pela conclusão de todos os workers lendo do canal
done
.
Caso de Uso 3: Processamento de Dados em Lote
Suponha que você tenha uma grande quantidade de dados que precisa ser processada, como realizar transformações, validações ou cálculos em cada elemento do conjunto de dados.
Usar um Worker Pool pode acelerar esse processamento, permitindo que várias tarefas sejam executadas simultaneamente.
package main
import (
"fmt"
"time"
)
const numWorkers = 3
func processDataWorker(id int, data <-chan int, results chan<- int) {
for item := range data {
fmt.Printf("Worker %d processando item %d\n", id, item)
// Simula o processamento
time.Sleep(time.Second)
results <- item * 2
}
}
func main() {
data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
numItems := len(data)
dataChannel := make(chan int, numItems)
results := make(chan int, numItems)
for i := 1; i <= numWorkers; i++ {
go processDataWorker(i, dataChannel, results)
}
for _, item := range data {
dataChannel <- item
}
close(dataChannel)
// Coleta os resultados
for i := 1; i <= numItems; i++ {
result := <-results
fmt.Printf("Resultado: %d\n", result)
}
close(results)
}
Explicação passo a passo
Exemplo 3: Processamento de Dados em Lote
Este exemplo mostra o processamento de dados em lote usando um Worker Pool.
- Um canal
dataChannel
é criado para armazenar os dados a serem processados. Um canalresults
é usado para armazenar os resultados. - Um loop cria os workers chamando a função
processDataWorker
com um ID de worker, o canal de dados e o canal de resultados. - Os itens de dados são enviados para o canal
dataChannel
. - Os workers (goroutines) leem os itens do canal
dataChannel
, simulam o processamento e enviam o resultado para o canalresults
. - No final, coletamos e imprimimos os resultados lendo do canal
results
.
Caso de Uso 4: Requisições HTTP em Paralelo
Ao lidar com requisições HTTP, como recuperar dados de várias APIs, um Worker Pool pode ser eficaz para realizar essas operações em paralelo, melhorando a velocidade de coleta de dados.
package main
import (
"fmt"
"net/http"
)
const numWorkers = 5
func fetchURL(workerID int, urls <-chan string, results chan<- string) {
for url := range urls {
fmt.Printf("Worker %d buscando %s\n", workerID, url)
resp, err := http.Get(url)
if err == nil {
results <- fmt.Sprintf("%s: %d", url, resp.StatusCode)
} else {
results <- fmt.Sprintf("%s: Erro - %s", url, err)
}
}
}
func main() {
urls := []string{"https://www.example.com", "https://www.google.com", "https://www.github.com", "https://www.openai.com"}
numURLs := len(urls)
urlsChannel := make(chan string, numURLs)
results := make(chan string, numURLs)
for i := 1; i <= numWorkers; i++ {
go fetchURL(i, urlsChannel, results)
}
for _, url := range urls {
urlsChannel <- url
}
close(urlsChannel)
// Coleta os resultados
for i := 1; i <= numURLs; i++ {
result := <-results
fmt.Println(result)
}
close(results)
}
Explicação passo a passo
Exemplo 4: Requisições HTTP em Paralelo
Neste exemplo, múltiplos workers fazem requisições HTTP em paralelo.
- Um canal
urlsChannel
é criado para armazenar as URLs das requisições. Um canalresults
é usado para armazenar os resultados. - Um loop cria os workers chamando a função
fetchURL
com um ID de worker, o canal de URLs e o canal de resultados. - As URLs das requisições são enviadas para o canal
urlsChannel
. - Os workers (goroutines) leem as URLs do canal
urlsChannel
, fazem requisições HTTP e enviam os resultados ou erros para o canalresults
. - No final, coletamos e imprimimos os resultados lendo do canal
results
.
Esses exemplos demonstram diferentes casos de uso de Worker Pools, onde tarefas são distribuídas entre workers para execução concorrente, melhorando a eficiência e o desempenho das operações.
Finalizando a aula
Compreendemos o poder de executar tarefas de maneira paralela, otimizando recursos e aumentando a eficiência.
Através de casos de uso reais, aprendemos a aplicar esse conceito para processar downloads, gerar relatórios complexos, lidar com processamento em lote e realizar requisições HTTP em paralelo.
Ao implementar Worker Pools, podemos aproveitar ao máximo a capacidade de processamento, resultando em programas mais eficazes e velozes.