Golang: Desmistificando channels - Buffered Channels
Published: 2022-10-01 21:22:43

## 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.

```go
// 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"
```

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.

```go
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
```

## 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.

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

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
```

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

```go
// 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)
```

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

```go
for {
	limitter <- true

	go func() {
		...
	}()
}
```

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

```go
for {
	limitter <- true

	go func() {
		...
		<-limitter
	}()
}
```

O resultado vai ser:

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