Aula 40 – Tutorial Golang – Atomic Counters

Aula 40 – Tutorial Golang – Atomic Counters

Tutorial Golang

Tutorial Golang

Página principal do blog

Todas as aulas desse curso

Aula 38                        Aula 40

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 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.

Na programação concorrente, tanto “workpools” (grupo de trabalho concorrente) quanto “atomic counters” (contadores atômicos) são técnicas utilizadas para gerenciar e sincronizar o acesso a recursos compartilhados entre múltiplas threads, em Go, goroutines, mas eles servem a propósitos diferentes e são aplicados em cenários distintos.

Vamos explorar as diferenças principais:

Workpools

  1. Propósito: Workpools são usados para distribuir a carga de trabalho entre um conjunto fixo ou dinâmico de workers (trabalhadores) que executam tarefas de forma concorrente. O objetivo é limitar o número de tarefas que são executadas ao mesmo tempo, gerenciando os recursos disponíveis de forma eficiente e evitando sobrecarga.
  2. Como Funciona: Uma workpool cria um número específico de goroutines (ou threads, em outras linguagens) que ficam esperando por tarefas para executar. As tarefas são tipicamente armazenadas em um canal ou fila. Cada worker pega uma tarefa da fila, executa-a, e então retorna à fila para pegar a próxima tarefa, continuando esse processo até que não haja mais tarefas para serem executadas.
  3. Uso: Ideal para cenários onde você tem muitas tarefas que precisam ser processadas, mas quer limitar o paralelismo para não exceder os recursos do sistema ou para manter a performance dentro de limites aceitáveis.

Atomic Counters

  1. Propósito: Contadores atômicos são utilizados para realizar operações de contagem ou de atualização de valores de forma segura em um ambiente concorrente. Eles garantem que cada operação sobre o contador (como incremento ou decremento) seja atômica, ou seja, completada integralmente sem interferência de outras operações concorrentes.
  2. Como Funciona: Operações atômicas são realizadas utilizando primitivas de baixo nível que são garantidas pela CPU ou pelo sistema operacional para serem indivisíveis. Isso significa que mesmo se múltiplas threads ou goroutines tentarem atualizar o contador ao mesmo tempo, cada atualização será processada de forma isolada, evitando condições de corrida. Condições de corrida ocorre quando dois ou mais processos (threads, goroutines, etc.) tentam ler e escrever dados compartilhados ao mesmo tempo, e o resultado final depende da ordem não determinística de execução desses acessos
  3. Uso: São ideais para cenários onde é necessário garantir a precisão de contadores ou de estados compartilhados em ambientes de alta concorrência, como contabilizar requisições em um servidor web ou contar eventos em sistemas de monitoramento.

Diferenças Chave

  • Aplicação: Workpools são mais sobre a organização e distribuição de tarefas entre workers, enquanto contadores atômicos são sobre garantir a precisão e consistência de operações específicas (como incrementar um contador) em um ambiente de concorrência.
  • Complexidade de Gerenciamento: Workpools geralmente requerem uma lógica de gerenciamento mais complexa, incluindo a criação de workers, distribuição de tarefas, e possivelmente o balanceamento de carga entre os workers. Contadores atômicos, por outro lado, são utilizados diretamente onde é necessário garantir a atomicidade de operações individuais.
  • Escopo de Uso: Workpools são usados para controlar como as tarefas são executadas em um nível macro, enquanto contadores atômicos são usados para operações seguras em variáveis compartilhadas em um nível micro.

Ambas as técnicas são fundamentais para o desenvolvimento de aplicações concorrentes robustas e eficientes, mas a escolha entre uma ou outra depende das necessidades específicas do cenário de aplicação.

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 chamado ops, que será usado para contar eventos (operações) de forma segura entre múltiplas goroutines.
    • var wg sync.WaitGroup: Utiliza um WaitGroup 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 contador ops 1000 vezes. Isso é feito de maneira atômica com ops.Add(1), garantindo que não haja condições de corrida no acesso ao contador.
  • Sincronização das Goroutines:
    • wg.Add(1) informa ao WaitGroup que uma goroutine está sendo iniciada, e wg.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 chamado wg.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 com ops.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.

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

  1. Contadores Atômicos para Opções de Voto: Aqui, votesOptionA e votesOptionB são contadores atômicos que rastreiam os votos para duas opções diferentes, A e B.
  2. 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.
  3. 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.
  4. 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.

Eu fico por aqui.

A gente se vê na próxima. \o/

Página principal do blog

Todas as aulas desse curso

Aula 38                        Aula 40

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>