Golang previnindo data race
No projeto do Ricardo Gomes onde ganhamos uma bela performace também acabamos acidentalmente incluindo uma bela data race.
Veja o vídeo desse arquivo aqui.
Data race acontece quando você tem acessos simultâneos a mesma variável por duas threads diferentes.
Mas Golang tem ferramentas para tudo, e nessa brincadeira eu aprendi sobre o parâmetro -race. Esse parâmetro faz com que o Golang 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 rodar ele 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 Golang 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 golang 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 ultimo detalhe, esse código vai sempre retornar 1 no valor de X, isso porque a função main acaba rápido demais e Golang 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.