Go previnindo data race

Como detectar e prevenir data race com Golang

No projeto do Ricardo Gomes onde ganhamos uma bela performace também acabamos acidentalmente incluindo uma bela data race.

Data race acontece quando você tem acessos simultâneos a 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 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 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 ultimo detalhe, esse código vai sempre retornar 1 no valor de X, isso porque a função main acaba rápido demais e 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.

comments powered by Disqus