Desenvolvimento

24 mar, 2017

Go previnindo data race

Publicidade

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.