DEV Community

Yan.ts
Yan.ts

Posted on

6 3

GO: Paralelismo e concorrência

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

Image description

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)
    }
}



Enter fullscreen mode Exit fullscreen mode

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)

Image description

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()
}


Enter fullscreen mode Exit fullscreen mode

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)
}


Enter fullscreen mode Exit fullscreen mode

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()
}



Enter fullscreen mode Exit fullscreen mode

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

Image description

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

Image description

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()
}


Enter fullscreen mode Exit fullscreen mode

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

Image description

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay