O que são e como utilizar channels

Algumas semanas atrás fizemos um post falando sobre o que são e como funcionam as goroutines (link do post). Nesse post, mencionamos sobre os channels quando fizemos um “fix” para que o programa esperasse a execução da goroutine antes de terminar.

No post de hoje vamos aprofundar um pouco sobre o que são os channels e como podemos usá-los.

Podemos pensar em channels como sendo uma espécie de tunel de comunidação entre goroutines, onde uma goroutine consegue enviar informações para outra antes mesmo de terminar sua execução. Nesse mesmo cenário, a goroutine que recebe a informação, ficaria pausada até as informações chegarem.

Quando vamos declarar um channel ou inicializa-lo, precisamos associar um tipo de dado a ele. Esse tipo de dado é o tipo que ele estará apto à transportar entre goroutines.

Ao declarar uma channel, seu valor inicial será nil. Para inicia-lo, devemos usar a função make do Go.

var c chan int
fmt.Printf("c é nil: %v\n", c == nil)

c = make(chan int)
fmt.Printf("c é nil: %v", c == nil)

Normalmente channels são declarados e inicializados ao mesmo tempo.

c := make(chan int)

Para escrever ou ler informações de um channel utilizamos o sinal <- que serve como uma seta indicadora. Se ela estiver apontando para o channel estamos escrevendo, do contrário estamos lendo.

data := &lt;- c // lendo
c &lt;- pessoa // escrevendo

Por padrão, sempre que há uma escrita ou leitura em um channel, ambas goroutines ficam bloqueadas. No caso, quando uma goroutine escreve algo em um channel, ela fica bloqueada até que outra goroutine leia. Já para goroutines que fazem leitura de um channel, essas ficam bloqueadas até que outra goroutine escreva algo no channel.

Caso uma goroutine escreva em um channel mas nenhuma outra faça a leitura, ou uma goroutine faça a leitura mas nenhuma outra escreva, o programa irá parar com um erro de deadlock.

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:  
main.main()  
    /tmp/sandbox046150166/main.go:12 +0x50

Agora que já temos algum conhecimento teórico, vamos meter a mão na massa e fazer do jeito certo aquele fix do exemplo que demos no post sobre goroutines.

package main

import "fmt"

func hello(done chan bool) {
    fmt.Println("eu sou uma nova goroutine")
    done &lt;- true
}

func main() {
    done := make(chan bool)
    go hello(done)
    &lt;-done
    fmt.Println("eu sou a goroutine principal")
}

Removemos o time.Sleep e modificamos a assinatura da função hello para que ela pudesse receber o channel que criamos dentro da execução principal.

E por falar em assinatura de função, no exemplo anterior estamos passando um channel bidirecional para a função hello, ou seja, ela pode ler e escrever no channel. No caso dessa função, ela poderia receber um channel direcional, ou seja, que ela possa somente ler ou escrever.

Para fazer essa definição, tudo o que precisamos fazer é adicionar uma seta na assinatura da função, onde <-chan type diz que o channel é somente leitura e chan<- type somente escrita.

package main

import "fmt"

func hello(done chan &lt;- bool) {
    fmt.Println("eu sou uma nova goroutine")
    done &lt;- true
}

func main() {
    done := make(chan bool)
    go hello(done)
    &lt;-done
    fmt.Println("eu sou a goroutine principal")
}

Com essa pequena mudança, garantimos que a função hello irá somente escrever no channel done.

Para finalizar, vamos falar sobre fechamento de channels.

Quando fazemos leitura de um channel, podemos adicionar uma variável extra que nos diz que estamos lendo de um channel aberto ou fechado.

v, ok := &lt;-c

ok será true quando o channel estiver aberto e false quando estiver fechado.

package main

import "fmt"

func numeros(v chan&lt;- int) {
    for i := 0; i &lt; 10; i++ {
        v &lt;- i
    }
    close(v)
}

func main() {
    c := make(chan int)
    go numeros(c)

    for {
        v, ok := &lt;-c
        if ok == false {
            break
        }
        fmt.Println("valor:", v)
    }
}

Uma outra forma mais simples para escrever esse mesmo código seria utilizando for range.

package main

import "fmt"

func numeros(v chan&lt;- int) {
    for i := 0; i &lt; 10; i++ {
        v &lt;- i
    }
    close(v)
}

func main() {
    c := make(chan int)
    go numeros(c)

    for v := range c {
        fmt.Println("valor:", v)
    }
}

Loops do tipo for range ficam lendo dados de um channel até que o channel seja fechado.

Deixem suas dúvidas nos comentários.

Até a próxima!


Subscreva

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

4 comentários sobre “O que são e como utilizar channels

Deixe uma resposta