Aula 36 – Tutorial Golang – Worker Pools

Aula 36 – Tutorial Golang – Worker Pools

Tutorial Golang

Tutorial Golang

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!

Pacote Programador Fullstack

Pacote Programador Fullstack

Página principal do blog

Todas as aulas desse curso

Aula 35                        Aula 37

Redes Sociais:

facebook

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

PIX Nubank

PIX Nubank


 

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. Economia de Recursos: Ao aproveitar o paralelismo, os Worker Pools permitem um processamento mais rápido, economizando recursos de tempo e energia.
  8. 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.

  1. Um canal jobs é criado para armazenar as URLs que precisam ser baixadas. Um canal done é usado para indicar quando cada worker concluiu seu trabalho.
  2. Um loop cria os workers chamando a função downloadWorker com um ID do worker, o canal jobs e o canal done.
  3. Para cada URL, ela é enviada para o canal jobs.
  4. Os workers (goroutines) leem as URLs do canal jobs, simulam o download e enviam uma confirmação para o canal done quando terminam.
  5. 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.

  1. Um canal tasks é criado para armazenar as tarefas de geração de relatório. Um canal done é usado para indicar quando cada worker concluiu seu trabalho.
  2. Um loop cria os workers chamando a função generateReportWorker com um ID de worker, o canal de tasks e o canal done.
  3. As tarefas de geração de relatório são enviadas para o canal tasks.
  4. Os workers (goroutines) leem as tarefas do canal tasks, simulam o processamento e enviam uma confirmação para o canal done quando terminam.
  5. 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.

  1. Um canal dataChannel é criado para armazenar os dados a serem processados. Um canal results é usado para armazenar os resultados.
  2. Um loop cria os workers chamando a função processDataWorker com um ID de worker, o canal de dados e o canal de resultados.
  3. Os itens de dados são enviados para o canal dataChannel.
  4. Os workers (goroutines) leem os itens do canal dataChannel, simulam o processamento e enviam o resultado para o canal results.
  5. 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.

  1. Um canal urlsChannel é criado para armazenar as URLs das requisições. Um canal results é usado para armazenar os resultados.
  2. Um loop cria os workers chamando a função fetchURL com um ID de worker, o canal de URLs e o canal de resultados.
  3. As URLs das requisições são enviadas para o canal urlsChannel.
  4. Os workers (goroutines) leem as URLs do canal urlsChannel, fazem requisições HTTP e enviam os resultados ou erros para o canal results.
  5. 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.

Eu fico por aqui e agradeço a você pela audiência.

Até mais. 🙂

Página principal do blog

Todas as aulas desse curso

Aula 35                        Aula 37

Meus links de afiliados:

Hostinger

Digital Ocean

One.com

Obrigado e bons estudos. 😉

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>