Sobes.tech
Middle

O que é importante lembrar ao trabalhar com mutexes em Go?

sobes.tech IA

Resposta da IA

Ao trabalhar com mutexes (sync.Mutex) em Go, é importante lembrar o seguinte:

  • Capturar e libertar: É necessário adquirir (Lock()) o mutex antes de aceder aos dados partilhados e libertá-lo (Unlock()) após terminar com eles. Usar defer mu.Unlock() imediatamente após mu.Lock() é uma boa prática para evitar esquecimentos.
  • Exclusividade: O mutex fornece acesso exclusivo a um recurso a um único goroutine de cada vez.
  • Estado "bloqueado/desbloqueado": Chamar Lock() num mutex já bloqueado fará com que o goroutine fique bloqueado até que outro o liberte. Chamar Unlock() num mutex não bloqueado causará um pânico em tempo de execução.
  • Não copiar mutexes: A estrutura sync.Mutex não foi concebida para ser copiada após o seu primeiro uso. Passar um mutex por valor ou copiar a estrutura que o contém pode levar a uma sincronização incorreta e a panics.
  • Deadlocks (bloqueios mútuos): Ocorrem quando duas ou mais goroutines esperam que a outra libere recursos. Exemplo típico: a goroutine A bloqueia R1 e espera R2, enquanto que a goroutine B bloqueia R2 e espera R1.
  • Livelocks (bloqueios vivos): Situação onde as goroutines mudam constantemente de estado em resposta às ações umas das outras, sem realizar trabalho útil nem avançar. É menos comum do que deadlock.
  • Fome (starvation): Situação onde uma ou várias goroutines não conseguem aceder a um recurso partilhado porque este está constantemente bloqueado por outras. Nos mutexes do Go, a fome não é garantida.
  • Uso de sync.RWMutex para leitura/escrita: Se a maioria das operações for leitura e as escritas forem raras, sync.RWMutex pode ser mais eficiente. Permite múltiplas leituras simultâneas, mas bloqueia durante uma escrita.
  • Bloqueios curtos: Tente manter os bloqueios o mais curtos possível. Quanto mais tempo um mutex estiver bloqueado, mais goroutines esperarão, reduzindo o paralelismo.
  • Testar condições de corrida: Usar a flag -race ao compilar e executar testes (go test -race) ajuda a detectar condições de corrida, muitas vezes causadas por uso incorreto de mutexes.

Exemplo de uso de defer:

import (
	"sync"
)

type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

// Inc incrementa de forma segura o contador para uma chave dada.
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// Liberamos ao sair da função.
	defer c.mu.Unlock()
	c.v[key]++
}

// Value devolve o valor atual do contador para uma chave dada.
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// Libertamos após obter o valor.
	defer c.mu.Unlock()
	return c.v[key]
}