Go by Example: Atomic Counters

El mecanismo principal para manejar el estado en Go es la comunicación a través de canales. Esto lo vimos, por ejemplo, con pools de trabajadores. Sin embargo, hay algunas otras opciones para manejar el estado. Aquí vamos a ver el uso del paquete sync/atomic para contadores atómicos accedidos por múltiples goroutines.

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

Vamos a utilizar un tipo de entero atómico para representar nuestro contador (siempre positivo).

    var ops atomic.Uint64

Un WaitGroup nos ayudará a esperar a que todas las goroutines terminen su trabajo.

    var wg sync.WaitGroup

Iniciaremos 50 goroutines que incrementan el contador exactamente 1000 veces cada una.

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

Para incrementar el contador de forma atómica utilizamos Add.

                ops.Add(1)
            }
            wg.Done()
        }()
    }

Espera hasta que todas las goroutines hayan terminado.

    wg.Wait()

Aquí ninguna goroutine está escribiendo en ‘ops’, pero utilizando Load es seguro leer atómicamente un valor incluso mientras otras goroutines lo están actualizando (atómicamente).

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

Esperamos obtener exactamente 50,000 operaciones. Si hubiéramos usado un entero no atómico e incrementado con ops++, probablemente obtendríamos un número diferente, cambiando entre ejecuciones, porque las goroutines interferirían entre sí. Además, tendríamos fallos de carrera de datos al ejecutar con la bandera -race.

$ go run atomic-counters.go
ops: 50000

A continuación, veremos los mutex, otra herramienta para manejar el estado.

Siguiente ejemplo: Mutexes.