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.

Cesar Gimenes

Última modificação