Médio Go

Contador concorrente com condição de corrida

Enunciado

Crie um programa que inicie 100 goroutines, cada uma incrementando uma variável compartilhada contador 1000 vezes. Use sync.WaitGroup para esperar. Execute com go run -race e observe o aviso de condição de corrida. Depois, corrija usando sync.Mutex para proteger o incremento. O programa deve imprimir o valor final correto (100000).

Requisitos

  • Iniciar 100 goroutines com go func().
  • Cada goroutine incrementa contador 1000 vezes.
  • Usar sync.WaitGroup para esperar todas.
  • Corrigir a condição de corrida com sync.Mutex.
  • Imprimir contador igual a 100000.

Código inicial

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	contador := 0
	// TODO: adicionar mutex, iniciar goroutines e proteger o incremento

	wg.Wait()
	fmt.Println("Contador:", contador)
}

Saída esperada

Contador: 100000
Ver dica

Use var mu sync.Mutex e envolva contador++ com mu.Lock() e mu.Unlock(). Rode com -race para ver o erro antes de corrigir.

Mostrar solução
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	var mu sync.Mutex // protege o acesso a contador
	contador := 0

	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 1000; j++ {
				mu.Lock()
				contador++
				mu.Unlock()
			}
		}()
	}

	wg.Wait()
	fmt.Println("Contador:", contador)
}

Passo a passo

  1. Declaramos var wg sync.WaitGroup e var mu sync.Mutex.
  2. contador é a variável compartilhada que será incrementada.
  3. Loop de 0 a 99: para cada iteração, wg.Add(1) e iniciamos uma goroutine.
  4. Dentro da goroutine, defer wg.Done() garante a decrementação ao final.
  5. Loop interno de 0 a 999: cada iteração faz mu.Lock(), contador++, mu.Unlock().
  6. Após o loop externo, wg.Wait() bloqueia até todas as goroutines terminarem.
  7. Imprimimos o valor final, que deve ser 100000.

Por que funciona

O Mutex garante exclusão mútua: apenas uma goroutine por vez executa o trecho crítico contador++. Sem ele, múltiplas goroutines podem ler e escrever o mesmo valor simultaneamente, causando incrementos perdidos e resultado menor que 100000. O WaitGroup assegura que a main espere todas as goroutines antes de imprimir.

Erros comuns

  • Esquecer mu.Lock()/mu.Unlock(): condição de corrida detectada pelo -race. Corrija protegendo o acesso.
  • Usar mu.Lock() sem defer mu.Unlock() dentro de um loop: se ocorrer panic, o mutex fica travado. Prefira defer em funções auxiliares.
  • Copiar o mutex (ex.: passar por valor): o mutex não protege nada. Sempre use ponteiro ou a variável original.

Outra forma de resolver

Usar sync/atomic:

var contador int64
// ...
atomic.AddInt64(&contador, 1)

É mais eficiente para contadores simples, mas menos geral que mutex.

Saída esperada

Contador: 100000