Aula 40 – Tutorial Golang – Atomic Counters
Aula 40 – Tutorial Golang – Atomic Counters
Página principal do blog
Todas as aulas desse curso
Aula 39 Aula 41
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 40 – Tutorial Golang – Atomic Counters
Go foi feito para facilitar o desenvolvimento de programas que podem executar várias tarefas simultaneamente, de forma eficiente e mais simples de entender e manter do que em outras linguagens.
Isso é chamado de programação concorrente.
Vamos ver agora 3 exemplos comuns de uso do pacote sync/atomic
em Go.
1. Contadores Atômicos em Go: Sincronização Concorrente
Cenário: Consideremos um cenário onde precisamos de um contador global para rastrear o número de operações ou eventos ocorridos, acessível por múltiplas goroutines. A abordagem tradicional poderia envolver o uso de mutexes para evitar o acesso simultâneo, o que pode ser menos eficiente. Utilizando o sync/atomic
, podemos implementar um contador atômico que garante a precisão e a performance, mesmo sob alta concorrência.
O exemplo a seguir demonstra a criação de um contador atômico para rastrear o número de operações realizadas por 50 goroutines, cada uma incrementando o contador 1000 vezes. Utilizamos sync.WaitGroup
para sincronizar a conclusão das goroutines, garantindo que a leitura final do contador aconteça apenas após todas as operações serem concluídas. Este método oferece uma solução eficaz e performática para gerenciamento de estado concorrente em Go.
Código
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
// Vamos usar um tipo primitivo uint64 para representar nosso
// contador (sempre positivo) e realizar operações atômicas sobre ele.
var ops uint64
// Um WaitGroup nos ajudará a esperar que todas as goroutines
// terminem seu trabalho.
var wg sync.WaitGroup
// Vamos iniciar 50 goroutines que cada uma incrementa o
// contador exatamente 1000 vezes.
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
for c := 0; c < 1000; c++ {
// Para incrementar atomicamente o contador usamos `AddUint64`.
atomic.AddUint64(&ops, 1)
}
wg.Done()
}()
}
// Esperar até que todas as goroutines estejam concluídas.
wg.Wait()
// Aqui nenhuma goroutine está escrevendo em 'ops', mas usando
// `LoadUint64` é seguro ler atomicamente um valor mesmo enquanto
// outras goroutines estão atualizando-o (atomicamente).
fmt.Println("ops:", atomic.LoadUint64(&ops))
}
Explicação:
- Declaração de Variáveis:
var ops uint64
: Declara um contador atômico chamadoops
, que será usado para contar eventos (operações) de forma segura entre múltiplas goroutines.var wg sync.WaitGroup
: Utiliza umWaitGroup
para sincronizar a conclusão de todas as goroutines antes de seguir.
- Execução de Goroutines:
- Inicia 50 goroutines (
for i := 0; i < 50; i++
), cada uma incrementando o contadorops
1000 vezes. Isso é feito de maneira atômica comops.Add(1)
, garantindo que não haja condições de corrida no acesso ao contador.
- Inicia 50 goroutines (
- Sincronização das Goroutines:
wg.Add(1)
informa aoWaitGroup
que uma goroutine está sendo iniciada, ewg.Done()
é chamado dentro de cada goroutine após completar seu loop, indicando que terminou sua execução.wg.Wait()
bloqueia a execução até que todas as goroutines tenham chamadowg.Done()
, garantindo que todas as operações de incremento tenham sido concluídas antes de prosseguir.
- Leitura do Contador:
- Após todas as goroutines terminarem, o valor final do contador
ops
é lido comops.Load()
. Essa operação também é atômica, assegurando uma leitura segura do valor final do contador, que reflete todas as 50.000 operações de incremento realizadas pelas goroutines.
- Após todas as goroutines terminarem, o valor final do contador
O código ilustra a eficácia do uso de operações atômicas para evitar condições de corrida e a importância da sincronização entre goroutines para garantir que o estado compartilhado seja atualizado e lido de forma segura.
2. Contador Atômico para Monitoramento de Estado
Cenário: Um serviço web onde múltiplas goroutines manipulam um contador que rastreia o número total de requisições recebidas.
Código
package main
import (
"fmt"
"net/http"
"sync/atomic"
)
var (
requestCount uint64
)
func requestHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
atomic.AddUint64(&requestCount, 1)
}
fmt.Fprintf(w, "Request number: %d", atomic.LoadUint64(&requestCount))
}
func main() {
http.HandleFunc("/", requestHandler)
fmt.Println("Servidor rodando na porta 8080...")
http.ListenAndServe(":8080", nil)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("Erro ao iniciar o servidor: %v\n", err)
}}
Explicação:
requestCount
é um contador atômico usado para rastrear o número de requisições.- Cada vez que uma requisição é recebida,
atomic.AddUint64(&requestCount, 1)
incrementa o contador de forma atômica. atomic.LoadUint64(&requestCount)
é usado para ler o valor atual do contador de forma atômica e segura para exibir ao usuário.- Isso garante que o contador seja incrementado corretamente, mesmo com múltiplas requisições simultâneas.
3. Sistema Básico de Votação em Tempo Real
Cenário: Imagine um cenário em que usamos contadores atômicos para implementar um sistema básico de votação em tempo real em uma aplicação web. Neste exemplo, cada requisição incrementa um contador atômico correspondente a uma opção de voto. Isso pode ser útil, por exemplo, em um sistema de enquetes ao vivo ou em uma funcionalidade de reação em tempo real, onde precisamos garantir a contagem precisa de votos sob alta concorrência.
Exemplo de Código: Sistema de Votação em Tempo Real com Contadores Atômicos
package main
import (
"fmt"
"net/http"
"strconv"
"sync/atomic"
)
var (
// Contadores atômicos para cada opção de voto
votesOptionA uint64
votesOptionB uint64
)
func voteHandler(w http.ResponseWriter, r *http.Request) {
option := r.URL.Query().Get("option")
switch option {
case "A":
atomic.AddUint64(&votesOptionA, 1)
fmt.Fprintf(w, "Voto para a opção A registrado. Total: %d\n", atomic.LoadUint64(&votesOptionA))
case "B":
atomic.AddUint64(&votesOptionB, 1)
fmt.Fprintf(w, "Voto para a opção B registrado. Total: %d\n", atomic.LoadUint64(&votesOptionB))
default:
http.Error(w, "Opção inválida", http.StatusBadRequest)
}
}
func resultsHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Resultados da Votação:\nOpção A: %d votos\nOpção B: %d votos\n",
atomic.LoadUint64(&votesOptionA),
atomic.LoadUint64(&votesOptionB))
}
func main() {
http.HandleFunc("/vote", voteHandler)
http.HandleFunc("/results", resultsHandler)
fmt.Println("Servidor de votação iniciado na porta 8080")
http.ListenAndServe(":8080", nil)
}
Teste:
curl "http://localhost:8080/vote?option=A"
curl "http://localhost:8080/vote?option=B"
curl "http://localhost:8080/results"
Explicação do Código
- Contadores Atômicos para Opções de Voto: Aqui,
votesOptionA
evotesOptionB
são contadores atômicos que rastreiam os votos para duas opções diferentes, A e B. - Handler de Votação: A função
voteHandler
é responsável por processar as requisições de voto. Ela verifica qual opção foi votada (A ou B) e incrementa o contador atômico correspondente. - Handler de Resultados: A função
resultsHandler
mostra os resultados atuais da votação, acessando de forma atômica os valores dos contadores de votos. - Servidor HTTP: O servidor está configurado para responder a requisições de votação e exibição de resultados nas rotas
/vote
e/results
, respectivamente.
Este exemplo ilustra como contadores atômicos podem ser usados em uma aplicação web para gerenciar um sistema de votação em tempo real.
Em um cenário com muitos usuários votando simultaneamente, os contadores atômicos garantem que cada voto seja contabilizado de forma precisa e segura, evitando condições de corrida e inconsistências nos dados.