DEV Community

Igor Melo
Igor Melo

Posted on

Golang: Desmistificando channels - Buffered Channels

Buffered channels

Se você já conseguiu entender e praticar bem a parte de channels, podemos falar sobre buffered channels.

Buffered channels são channels que não bloqueiam o envio e recebimento em alguns cenários, porque eles guardam os valores enviados temporariamente num buffer, que vai ser lido e removido do buffer quando alguém receber.

Você cria um buffered channel com o tamanho do buffer que você quer, por exemplo 5, daí você vai poder fazer 5 envios sem bloquear a goroutine.

A partir daí, enquanto o buffer estiver cheio, os próximos envios vão bloquear, da mesma forma que num channel sem buffer.

// basta passar um tamanho na hora de criar o channel
ch := make(chan string, 3)

// não vão bloquear a gourotine
ch <- "A"
ch <- "B"
ch <- "C"

// buffer cheio, novos envios vão bloquear a goroutine 
// até que surja um  espaço livre no buffer
ch <- "D"
Enter fullscreen mode Exit fullscreen mode

Da mesma forma, receber de um buffered channel não vai bloquear se o buffer não estiver vazio.

Se ele estiver vazio, vai funcionar como um channel sem buffer.

ch := make(chan string, 3)

// não vão bloquear a gourotine
ch <- "A"
ch <- "B"
ch <- "C"

// não vão bloquear a goroutine
<-ch // "A"
<-ch // "B"
<-ch // "C"

// buffer vazio, vai bloquear a goroutine até um novo envio
<-ch
Enter fullscreen mode Exit fullscreen mode

Usando buffered channels para controlar número de goroutines

Uma grande vantagem dos buffered channels é que eles passam a bloquear quando o buffer está cheio, e podemos usar isso ao nosso favor.

Nesse exemplo aqui eu tenho um programa que vai ficar criando goroutines indeterminadamente e cada goroutine faz uma requisição GET.

package main

import (
    "fmt"
    "net/http"
)

func main() {
    for {
        go func() {
            client := http.Client{}

            resp, _ := client.Get("https://www.google.com")
            resp.Body.Close()

            fmt.Println(resp.Status)
        }()
    }
}
Enter fullscreen mode Exit fullscreen mode

O problema é que não podemos ficar criando goroutines e clientes HTTP sem limites, porque memória não é infinita, e nem vai ser muito eficiente fazer milhares de requisições ao mesmo tempo.

Esse programa vai cedo ou tarde dar um erro e vai encerrar:

"runtime error: invalid memory address or nil pointer dereference"

Stack:
    4  0x0000000000744764 in main.main.func1
        at /home/igor/Git/pessoal/got/main.go:21
Enter fullscreen mode Exit fullscreen mode

Para resolver isso, vamos criar um buffered channel antes do loop:

// o tipo não importa muito, porque não estamos interessados no valor enviado
// nesse caso vou limitar para 50 goroutines fazendo requisições
limitter := make(chan bool, 50)
Enter fullscreen mode Exit fullscreen mode

Toda vez antes de criar uma goroutine vamos adicionar um valor ao buffer:

for {
    limitter <- true

    go func() {
        ...
    }()
}
Enter fullscreen mode Exit fullscreen mode

Toda vez que a goroutine finalizar, vamos tirar um valor do buffer:

for {
    limitter <- true

    go func() {
        ...
        <-limitter
    }()
}
Enter fullscreen mode Exit fullscreen mode

O resultado vai ser:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    finished := make(chan bool, 50)
    for {
        finished <- true

        go func() {
            client := http.Client{}

            resp, _ := client.Get("https://www.google.com")
            resp.Body.Close()

            fmt.Println(resp.Status)
            <-finished
        }()
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)