Go Em Exemplos: Atomic Counters

O mecanismo primário para gerenciar estado em Go ẽ a comunicação entre canais. Um dos exemplos apresentados foi os worker pools. No entanto, existem algumas outras opções para gerenciar estado. Aqui será apresentado o pacote sync/atomic para contadores atômicos accessados por múltiplas goroutines.

package main
import (
    "fmt"
    "sync"
    "sync/atomic"
)
func main() {

Será utilizado um inteiro unsigned integer para representar um contador sempre positivo.

    var ops uint64

Um WaitGroup auxiliará a aguardar todas as goroutines finalizarem suas tarefas.

    var wg sync.WaitGroup

Iniciaremos 50 goroutines que incrementarão o contador exatamente 1000 vezes.

    for i := 0; i < 50; i++ {
        wg.Add(1)

Para incrementar o contador será utilizado AddUint64, passando como parâmetro o endereço de memória do contador ops. Note que para acessar o endereço de memória de determinado dado, se utiliza a sintaxe &.

        go func() {
            for c := 0; c < 1000; c++ {

ops++

                atomic.AddUint64(&ops, 1)
            }
            wg.Done()
        }()
    }

Aguarda todas as goroutines finalizarem.

    wg.Wait()

É seguro ler o contador ops uma vez que não há nenhuma goroutine escrevendo sobre ele. Para ler o contador enquanto ele ẽ atualizado, pode-se utilizar a função atomic.LoadUint64, por exemplo.

    fmt.Println("ops:", ops)
}

É esperado que o código realize exatamente 50.000 operações. Se fosse utilizada a forma não-atômica de incrementação, provavelmente o resultado seria diferente entre as execuções, porque as goroutines interfeririam umas com as outras. Alẽm disso, provavelmente aconteceria falhas de data race, que é possível visualizar executando o código com a flag -race (go run -race atomic-counters.go)

$ go run atomic-counters.go
ops: 50000

Código com ops++ ao invés de atomic.AddUint64(&ops, 1) sendo executado com a flag -race

$ go run -race atomic-counters.go
==================
WARNING: DATA RACE
Read at 0x00c00001a0f8 by goroutine 10:
  main.main.func1()
      /.../atomic-counters.go:38 +0x46
==================
ops: 6573
Found 1 data race(s)
exit status 66

Em seguida, será apresentado mutexes, outra ferramenta para gerenciar estado.

Próximo exemplo: Mutexes.