No projeto do Ricardo Gomes, onde ganhamos uma bela performace, também acabamos acidentalmente incluindo uma bela data race.
Uma data race acontece quando você tem acessos simultâneos a uma mesma variável por duas threads diferentes.
Mas Go tem ferramentas para tudo, e nessa brincadeira, eu aprendi sobre o parâmetro -race. Esse parâmetro faz com que o Go procure por possíveis data races em seu código e pode ser usado com test, run, build e install.
Testando o Data Race Detector
Vamos supor o seguinte código:
package main func main() { var x int go func() { x++ }() x++ println(x) }
Agora, tente rodá-lo passando o parâmetro -race para o run.
go run -race main.go
A saída vai ser como abaixo:
> $ go run -race main.go 1 ================== WARNING: DATA RACE Read at 0x00c42007c000 by goroutine 5: main.main.func1() /Users/cesar/test/main.go:7 +0x3b Previous write at 0x00c42007c000 by main goroutine: main.main() /Users/cesar/test/main.go:10 +0xa7 Goroutine 5 (running) created at: main.main() /Users/cesar/test/main.go:8 +0x7d ================== Found 1 data race(s) exit status 66
E como esperado, Go gerou um alerta avisando do data race mostrando as linhas, as goroutines etc.
Agora vamos modificar o código para resolver o problema. Poderíamos usar canais nesse caso, mas a forma mais simples e parecida com outras linguagens seria fazer um lock e unlock sempre que for ler e escrever nos recursos compartilhados entre as treads.
Veja o exemplo do código corrigido:
package main import "sync" func main() { var x int var m sync.Mutex go func() { m.Lock() x++ m.Unlock() }() m.Lock() x++ println(x) m.Unlock() }
E teste com go run -race main.go, como fizemos anteriormente. Você vai ver que o alerta sumiu, já que agora não tem mais o perigo de data race.
É impressionante a quantidade de ferramentas de análise estática que vem de fabrica no Go.
Um último detalhe: esse código vai sempre retornar 1 no valor de X, isso porque a função main acaba rápido demais e o Go limpa as goroutines assim que a função main terminar. Então, a nossa goroutine nunca vai ter tempo de adicionar nada em x; eu usei esse exemplo apenas para ilustrar usando o mínimo de código possível.