Aula 37 – Tutorial Golang – WaitGroups
Aula 37 – Tutorial Golang – WaitGroups
Página principal do blog
Todas as aulas desse curso
Aula 36 Aula 38
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 37 – Tutorial Golang – WaitGroups
Introdução
As WaitGroups são uma ferramenta poderosa em Go para sincronizar goroutines e controlar o fluxo de execução em programas concorrentes.
Elas são úteis quando você precisa garantir que todas as goroutines concluam suas tarefas antes que o programa principal termine ou antes de prosseguir para a próxima etapa.
Nesta aula, vamos explorar WaitGroups e aprender a usá-las com exemplos práticos e casos de uso.
O que é uma WaitGroup?
Uma WaitGroup é uma estrutura de dados fornecida pela biblioteca padrão de Go que permite esperar que um conjunto de goroutines seja concluído antes de continuar a execução do programa principal.
Ela faz isso mantendo uma contagem interna de goroutines e bloqueando o programa principal até que todas as goroutines tenham sinalizado que terminaram.
Exemplo 1: WaitGroup Básica
Vamos começar com um exemplo simples para entender como uma WaitGroup funciona.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
// Criando três goroutines simulando tarefas independentes
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d executando\n", id)
}(i)
}
// Aguardando todas as goroutines concluírem
wg.Wait()
fmt.Println("Todas as goroutines concluídas")
}
Importações:
O programa importa as bibliotecas “fmt” e “sync” para impressão de saída e manipulação de sincronização de concorrência, respectivamente.
Criação da WaitGroup:
É criada uma WaitGroup chamada “wg” usando a declaração var wg sync.WaitGroup.
Loop para Criar Goroutines:
Um loop for é usado para criar três goroutines simulando tarefas independentes.
wg.Add(1) é chamado dentro do loop para adicionar 1 ao contador da WaitGroup para cada goroutine que será criada.
Isso aumenta o contador em 1 para cada goroutine, indicando que três goroutines precisam ser esperadas.
Em seguida, é iniciada uma nova goroutine anônima com go func(id int) { … }(i).
Cada goroutine recebe um argumento “id” que é igual ao valor atual de “i” no loop.
Goroutines:
Cada goroutine imprime uma mensagem indicando que está executando e usa defer wg.Done() para adiar a chamada a wg.Done().
Isso significa que wg.Done() será chamado quando a goroutine for concluída, o que decrementará o contador da WaitGroup em 1.
Aguardando Todas as Goroutines:
Após a criação das três goroutines, o programa principal chama wg.Wait().
Isso faz com que o programa principal aguarde até que o contador da WaitGroup seja zero.
O programa fica bloqueado até que todas as três goroutines tenham chamado wg.Done().
Mensagem de Conclusão:
Após todas as goroutines serem concluídas, ou seja, o contador da WaitGroup ser zero, o programa imprime “Todas as goroutines concluídas” usando fmt.Println.
Exemplo 2: Uso em um Pool de Trabalhadores
Um caso de uso comum para WaitGroups é em um pool de trabalhadores, onde várias goroutines trabalham em tarefas diferentes e o programa principal aguarda a conclusão de todas as tarefas.
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d iniciado\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d concluído\n", id)
}
func main() {
var wg sync.WaitGroup
numWorkers := 5
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("Todas as tarefas concluídas")
}
Importações:
O programa importa as bibliotecas “fmt”, “sync” e “time”. “fmt” é usada para impressão de saída, “sync” para sincronização de concorrência e “time” para lidar com o tempo.
Função do Trabalhador (worker):
A função worker representa o trabalho que cada goroutine realizará.
defer wg.Done() é usado para garantir que wg.Done() seja chamado quando a goroutine for concluída.
Isso decrementa o contador da WaitGroup em 1.
fmt.Printf(“Worker %d iniciado\n”, id) imprime uma mensagem indicando que o trabalhador foi iniciado.
time.Sleep(time.Second) é usado para simular algum trabalho demorado (1 segundo de espera).
fmt.Printf(“Worker %d concluído\n”, id) imprime uma mensagem indicando que o trabalhador foi concluído.
Função Principal (main):
Uma WaitGroup chamada “wg” é criada para coordenar a sincronização das goroutines.
A variável “numWorkers” é definida para o número de goroutines que serão criadas (5 no caso).
Loop para Criar Goroutines:
Um loop for é usado para criar cinco goroutines, simulando cinco tarefas independentes.
wg.Add(1) é chamado dentro do loop para adicionar 1 ao contador da WaitGroup para cada goroutine que será criada.
Isso aumenta o contador em 1 para cada goroutine, indicando que cinco goroutines precisam ser esperadas.
Em seguida, é iniciada uma nova goroutine chamando go worker(i, &wg).
Cada goroutine recebe um argumento “i” que é igual ao valor atual do loop.
Aguardando Todas as Goroutines:
Após a criação das cinco goroutines, o programa principal chama wg.Wait().
Isso faz com que o programa principal aguarde até que o contador da WaitGroup seja zero.
O programa fica bloqueado até que todas as cinco goroutines tenham chamado wg.Done().
Conclusão:
Após todas as goroutines serem concluídas (ou seja, o contador da WaitGroup é zero), o programa imprime “Todas as tarefas concluídas” usando fmt.Println.
O time.Sleep(time.Second) no worker é usado para simular tarefas demoradas, e o defer wg.Done() garante que o contador da WaitGroup seja decrementado corretamente, mesmo se ocorrerem erros ou exceções nas goroutines.
Caso de Uso 3: Aguardando Várias Requisições HTTP
Você pode usar WaitGroups para aguardar a conclusão de várias requisições HTTP concorrentes.
Cada goroutine faz uma solicitação HTTP e, quando todas as solicitações forem concluídas, o programa principal pode processar os resultados.
package main
import (
"fmt"
"net/http"
"sync"
)
func fetchURL(url string, wg *sync.WaitGroup) {
defer wg.Done()
resp, err := http.Get(url)
if err != nil {
fmt.Printf("Erro ao buscar URL %s: %v\n", url, err)
return
}
defer resp.Body.Close()
fmt.Printf("URL %s retornou com status: %s\n", url, resp.Status)
}
func main() {
var wg sync.WaitGroup
urls := []string{"https://example.com", "https://google.com", "https://github.com"}
for _, url := range urls {
wg.Add(1)
go fetchURL(url, &wg)
}
wg.Wait()
fmt.Println("Todas as requisições HTTP concluídas")
}
Importações:
O programa importa as bibliotecas “fmt” para impressão de saída e “net/http” para realizar solicitações HTTP.
Função fetchURL:
A função fetchURL é responsável por buscar uma URL usando uma solicitação HTTP GET.
defer wg.Done() é usado para garantir que wg.Done() seja chamado quando a goroutine for concluída, o que decrementa o contador da WaitGroup em 1.
http.Get(url) faz uma solicitação HTTP GET à URL fornecida.
Se houver um erro durante a solicitação, ele é tratado e um erro é impresso na saída.
defer resp.Body.Close() é usado para garantir que o corpo da resposta HTTP seja fechado após o uso.
Finalmente, o status da resposta HTTP é impresso.
Função Principal (main):
Uma WaitGroup chamada “wg” é criada para coordenar a sincronização das goroutines.
Uma slice de URLs é definida em “urls“, contendo três URLs para buscar.
Loop para Buscar URLs:
Um loop for é usado para percorrer cada URL em “urls“.
wg.Add(1) é chamado dentro do loop para adicionar 1 ao contador da WaitGroup para cada URL, indicando que três URLs precisam ser buscadas.
Em seguida, é iniciada uma nova goroutine chamando go fetchURL(url, &wg).
Cada goroutine busca uma URL específica.
Aguardando Todas as Goroutines:
Após a criação das três goroutines, o programa principal chama wg.Wait().
Isso faz com que o programa principal aguarde até que o contador da WaitGroup seja zero.
O programa fica bloqueado até que todas as três goroutines tenham chamado wg.Done().
Conclusão:
Após todas as goroutines serem concluídas, isto é, o contador da WaitGroup ser zero, o programa imprime “Todas as requisições HTTP concluídas” usando fmt.Println.
Isso indica que todas as solicitações HTTP foram concluídas.
Caso de Uso 4: Processamento de Dados em Lote
Você pode usar WaitGroups ao processar dados em lote, onde várias goroutines processam partes diferentes dos dados.
O programa principal pode aguardar até que todas as partes dos dados tenham sido processadas antes de prosseguir.
package main
import (
"fmt"
"sync"
)
func processDataBatch(batch []int, wg *sync.WaitGroup) {
defer wg.Done()
sum := 0
for _, num := range batch {
sum += num
}
fmt.Printf("Soma dos elementos no lote: %d\n", sum)
}
func main() {
var wg sync.WaitGroup
data := []int{1, 2, 3, 4, 5, 6, 7, 8}
batchSize := 2
for i := 0; i < len(data); i += batchSize { wg.Add(1) end := i + batchSize if end > len(data) {
end = len(data)
}
go processDataBatch(data[i:end], &wg)
}
wg.Wait()
fmt.Println("Processamento de dados em lote concluído")
}
Importações:
O código faz uso da biblioteca padrão do Go, sem importações adicionais.
Função processDataBatch:
func processDataBatch(batch []int, wg *sync.WaitGroup): Esta função é responsável por processar um lote (ou batch) de números inteiros.
defer wg.Done(): Usa defer para adiar a chamada de wg.Done(), o que decrementa o contador da WaitGroup (wg) quando a goroutine é concluída.
Calcula a soma dos elementos no lote e imprime o resultado usando fmt.Printf.
Função main:
func main(): A função principal do programa.
Cria uma variável wg do tipo sync.WaitGroup para coordenar a execução das goroutines.
Define uma slice chamada data que contém os dados a serem processados, no caso, números de 1 a 8.
batchSize define o tamanho do lote a ser processado de uma vez, neste caso, 2.
Loop para Processar em Lotes:
O loop for é usado para dividir os dados em lotes e processá-los em paralelo.
wg.Add(1) é chamado dentro do loop para adicionar 1 ao contador da WaitGroup (wg) para cada lote, indicando quantos lotes precisam ser processados.
O índice end é calculado para determinar onde termina o lote atual.
Se o próximo lote estiver além do tamanho dos dados, ele é ajustado para o tamanho dos dados.
Uma nova goroutine é iniciada chamando go processDataBatch(data[i:end], &wg).
Cada goroutine processa um lote específico dos dados.
Aguardando Todas as Goroutines:
Após a criação das goroutines para processar os lotes, o programa principal chama wg.Wait().
Isso faz com que o programa principal aguarde até que o contador da WaitGroup (wg) seja zero.
O programa fica bloqueado até que todas as goroutines tenham chamado wg.Done().
Conclusão:
Após todas as goroutines terem processado os lotes, ou seja, o contador da WaitGroup ser zero, o programa imprime “Processamento de dados em lote concluído” usando fmt.Println.
Isso indica que o processamento dos dados em lotes foi concluído.