O que são e como funcionam as Goroutines

Na semana passada explicamos a diferença entre concorrência e paralelismo (link do post), o que serviu de base para o post de hoje, onde vamos explicar as famosas goroutines.

Goroutines são funções ou métodos executados em concorrência. Podemos pensar nelas como uma especie de lightweight thread que são gerenciadas pelo runtime do Go.

Chamamos de lightweight thread pois o custo para sua criação é muito menor quando comparada com um thread de verdade. Outro ponto positivo é que o runtime consegue aumentar ou diminuir a quantidade de goroutines de acordo com a necessidade da aplicação, enquanto o número de thread normalmente é fixo.

É comum uma única thread conter milhares de goroutines sendo executadas. Caso alguma dessas goroutines bloqueie a execução das outras por estar aguardando algum tipo de input do usuário, por exemplo, o runtime do Go irá criar uma nova thread e mover as outras goroutines para essa thread.

Goroutines rodam no mesmo espaço de memória, o que facilita para compartilhar objetos em memória, mas também devemos tomar cuidado para manter sincronizado. Os channels ajudam nesse processo de comunicação entre goroutines, além de evitar race condition.

Sempre que queremos iniciar uma nova goroutine, tudo o que precisamos fazer é adicionar a palavra go antes da chamada da função/método.

go foo(a, b, c)

foo, a, b e c são interpretadas na goroutine em que a chamada ocorreu, porém sua execução acontecerá em uma nova goroutine.

Calma, como assim na goroutine que a chamada ocorreu?!?!??!

Básicamente tudo em Go funciona com goroutines, inclusive a execução principal do programa. Por isso, qualquer chamada de uma nova goroutine acontece dentro de uma goroutine.

Ok, agora que temos todos em conceitos na cabeça, vamos escrever um programa bem simples para por em prática!

package main

import "fmt"

func hello() {
    fmt.Println("eu sou uma nova goroutine")
}

func main() {
    go hello()
    fmt.Println("eu sou a goroutine principal")
}

Agora quando executarmos o go run main.go teremos um output parecido com isso:

eu sou a goroutine principal

Calma…. cadê o output da goroutine que eu iniciei ali??????

Isso aconteceu por que o retorno de uma nova goroutine não é aguardado pela execução do programa. Após a nova goroutine ser iniciada, o programa já executa a próxima linha, que no nosso caso é o fmt.Println("eu sou a goroutine principal") .

Para fazer um “fix” no nosso código, vamos adicionar um time.Sleep(time.Second * 1) logo após o fmt da função main.

Agora sim conseguimos ver a execução da goroutine acontecendo.

Chamei a mudança de “fix” pois essa definitivamente não é a forma ideal de se esperar a execução de uma goroutine. Para isso existem os channels, mas para esse post não ficar muito extenso, vamos discuti-los em um outro post dedicado somente a eles.

Antes de terminar, vamos escrever mais um programa, agora com várias goroutines.

package main

import (
    "fmt"
    "time"
)

func numeros() {
    for i := 0; i < 10; i++ {
        fmt.Printf("%d ", i)
        time.Sleep(time.Millisecond * 200)
    }
}

func letras() {
    for l := 'a'; l < 'j'; l++ {
        fmt.Printf("%c ", l)
        time.Sleep(time.Millisecond * 300)
    }
}

func main() {
    go numeros()
    go letras()
    time.Sleep(5 * time.Second)
    fmt.Printf("fim da execução!\n")
}

Ao executar esse programas teremos o seguinte output:

$ 0 a 1 b 2 c 3 4 d 5 e 6 7 f 8 g 9 h i fim da execução!

Como podemos ver, as duas funções foram executadas de forma concorrente.

Deixem suas dúvidas nos comentários.

Até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

3 comentários sobre “O que são e como funcionam as Goroutines

Deixe uma resposta