GO: Paralelismo e concorrência
Yan.ts
Posted on May 29, 2022
Paralelismo e concorrência em GO foi na verdade o primeiro assunto que me fez ter interesse em aprender essa linguagem, apesar do meu objetivo inicial ter sido ver como ela tratava o paralelismo só hoje que eu fui de fato estudar sobre.
Diferenças entre concorrência e paralelismo
Na concorrência os processos são disparados ao mesmo tempo mas como é usado apenas um core, um processo vai parar para que o outro possa rodar e ai eles vão sendo intercalados.
No paralelismo os dois processos são iniciados ao mesmo tempo, mas cada um é processado em um core diferente do processador, fazendo assim com que os dois possam ser processados simultaneamente
Go routines
func main() {
runProcess("Process 1", 20)
runProcess("Process 2", 20)
}
func runProcess(name string, total int) {
for i := 0; i < total; i++ {
fmt.Println(name, i)
t := time.Duration(rand.Intn(255))
time.Sleep(time.Millisecond * t)
}
}
Essa função definida acima dessa forma inicial vai primeiro printar 20 vezes no terminal "Process 1" esperando sempre um tempo aleatório em milissegundos para o segundo print, e depois vai printar mais 20 vezes "Process 2".
Adicionando um operador go
na frente de cada uma da chamada das funções vai fazer com que ela rodem em paralelo e em background. Agora ao invés de esperarmos a primeira função rodar para só então a segunda ser executada, com o go
na frente enquanto uma função está esperando o timeout passar a outra é executada (se não definíssemos um timeout como é uma função muito simples na hora que o programa chegasse na segunda função a primeira já teria terminado de executar e não daria para ver o efeito da go routine)
porém se somente adicionar o go
na frente da função o terminal não vai exibir nada, por que as funções estão rodando em background então na verdade o go vai achar que elas já terminaram de rodar e terminar a execução. Para resolver esse problema precisamos criar um waitGroup e para fazer com que o Go espere a função terminar de rodar para ai sim matar a aplicação.
var waitGroup sync.WaitGroup
func main() {
waitGroup.Add(2)
go runProcess("Process 1", 20)
go runProcess("Process 2", 20)
waitGroup.Wait()
}
func runProcess(name string, total int) {
for i := 0; i < total; i++ {
fmt.Println(name, i)
t := time.Duration(rand.Intn(255))
time.Sleep(time.Millisecond * t)
}
waitGroup.Done()
}
No main adicionei duas funções ao waitGroup e na função runProcess falo que o waitGroup concluiu o trabalho depois do for.
Mas agora fica a questão, o Go está usando paralelismo ou concorrência? e na verdade depende, nesse caso é paralelismo pois tenho uma CPU de 6 cores então por default ele associa cada função a um core, porem se eu tivesse apenas 1 core no processador ele rodaria as duas funções de forma concorrente
podemos testar também rodar de forma concorrente adicionando uma função init e setando o numero máximo de cores que permitimos o GO usar. porem se fizermos isso para esse caso o output será o mesmo
func init() {
runtime.GOMAXPROCS(1)
}
Race conditions
Race conditions é um tipo de problemas que temos com o pararelismo onde a execução dos codigos em pararelo compromete de alguma forma as regras de negocio da aplicação, então por exemplo caso eu queira executar o total de vezes que o for rodou
var result int
var waitGroup sync.WaitGroup
func main() {
waitGroup.Add(2)
go runProcess("Process 1", 20)
go runProcess("Process 2", 20)
waitGroup.Wait()
fmt.Println("Result:", result)
}
func runProcess(name string, total int) {
for i := 0; i < total; i++ {
z := result
z++
t := time.Duration(rand.Intn(255))
time.Sleep(time.Millisecond * t)
result = z
fmt.Println(name, "->", i, result)
}
waitGroup.Done()
}
no meu println com mostrando o result que deveria ser 40 na verdade o output vai ser 20 e podemos ver que o valor de result de fato está sendo reatribuindo para um valor anterior constantemente por causa da execução anterior ainda não ter o novo valor que result deveria ter
se rodarmos o programa com go run -race main.go
o proprio go já detecta se está ocorrendo uma race condition e se sim em quais linhas
para resolver o problema de race condition é bem simples basta usarmos o Mutex onde travamos uma operação até ela terminar de rodar para impedir que ela seja sobescrita no meio
func runProcess(name string, total int) {
for i := 0; i < total; i++ {
t := time.Duration(rand.Intn(255))
time.Sleep(time.Millisecond * t)
m.Lock()
result++
fmt.Println(name, "->", i, "total", result)
m.Unlock()
}
waitGroup.Done()
}
Agora os processos ficam travados esperando o result terminar de aumentar o seu valor, e só quando dou o unlock eles continuam. Se rodarmos a aplicação detectando race conditions podemos ver que agora nada foi detectado
Posted on May 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.