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
contador1000 vezes. - Usar
sync.WaitGrouppara esperar todas. - Corrigir a condição de corrida com
sync.Mutex. - Imprimir
contadorigual 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
- Declaramos
var wg sync.WaitGroupevar mu sync.Mutex. contadoré a variável compartilhada que será incrementada.- Loop de 0 a 99: para cada iteração,
wg.Add(1)e iniciamos uma goroutine. - Dentro da goroutine,
defer wg.Done()garante a decrementação ao final. - Loop interno de 0 a 999: cada iteração faz
mu.Lock(),contador++,mu.Unlock(). - Após o loop externo,
wg.Wait()bloqueia até todas as goroutines terminarem. - 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()semdefer mu.Unlock()dentro de um loop: se ocorrer panic, o mutex fica travado. Prefiradeferem 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