Go Em Exemplos: Mutexes

No exemplo anterior, foi apresentado como gerenciar estados de contadores simples usando atomic operations. Para estados mais complexos é recomendado utilizar mutex para acessar dados entre múltiplas goroutines seguramente. MutEx é abreviação de Mutual Exclusion

package main
import (
    "fmt"
    "sync"
)

A struct container possui um map de contadores; como a intenção aqui é atualizar concorrentemente a partir de múltiplas goroutines, é adicionado Mutex para sincronizar o acesso. Note que mutex não deve ser copiado, então, se a struct é passada para outras funções ou etc, deve ser feito com ponteiro.

type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

Trava o mutex antes de acessar counters; destrava ao final da função utilizando a expressão defer. e incrementa determinado contador.

func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}

Note que o valor padrão, ou zero, de um mutex é utilizável, então não é necessária a inicialização aqui.

func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }
    var wg sync.WaitGroup

Esta função incrementa, em um loop, um contador de determinado nome.

    doIncrement := func(name string, n int) {
        for i := 0; i < n; i++ {
            c.inc(name)
        }
        wg.Done()
    }

Executando várias goroutines concorrentemente; Note que todas tem acesso ao mesmo Container, e duas delas tem acesso ao mesmo contador a.

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

Aguarda todas as goroutines finalizarem.

    wg.Wait()
    fmt.Println(c.counters)
}

Executando o programa, é exibido que os contadores foram atualizados, conforme esperado.

$ go run mutexes.go
map[a:20000 b:10000]

Em seguida, será apresentado como implementar este mesmo gerenciamento de estado de tarefas usando apenas goroutines e canais.

Próximo exemplo: Stateful Goroutines.