Golang: Desmistificando channels - Conceito e sintaxe
Tags: braziliandevs, go, programming, tutorial
Published: 2022-10-01 21:14:37

## Introdução

Resolvi fazer esse texto porque no meu primeiro contato com channels eu não entendi muito bem como funcionava, daí eu sempre fugia para o `WaitGroup` ou fazia alguma `Mutex` para usar goroutines, mesmo quando a melhor solução seria usar channels.

Fiz esse texto com o objetivo de deixar mais claro como usar channels e os problemas comuns que você vai encontrar ao usá-los de forma errada.

## O que é e para que serve?

Channel, ou canal, é uma forma nativa do Go para comunicar e sincronizar goroutines.

O channel já é _thread-safe_ por padrão, ou seja, enviar e receber são operações seguras de se usar com goroutines, então você não vai precisar se preocupar em criar *mutexes* nem nada do tipo.

Com channels você consegue facilmente fazer coisas como:

- Coletar o resultado de várias goroutines fazendo requisições HTTP e ir mostrando na tela
- Criar workers que processam no máximo 50 operações concorrentes
- Fazer outras goroutines esperarem por um sinal que sua aplicação iniciou

## Criando e usando channels

Para criar um channel, fazemos:

```go
func main() {
	ch := make(chan int)
}
```

O channel pode ser basicamente de qualquer tipo: `int`, `string`, `struct{}` e outros. 

Esse tipo é o tipo de dado que vai ser enviado e recebido pelo canal.
Detalhe que ao enviar uma informação para um channel, o que o outro lado recebe é uma cópia do dado.

Dessa forma você já evita vários problemas de data race, porque a goroutine que recebeu o dado pode ler e modificar sem o risco de afetar outra goroutine.
A exceção para isso é quando você cria channels de tipos que não copiam o dado inteiro, como ponteiros, maps e slices.

Exemplos de channels que vão copiar somente a referência, e não o dado completo:
```go
ch := make(chan *int)
ch := make(chan []string)
ch := make(chan map[string]User)
```

Para enviar informações para um channel, fazemos:

```go
func main() {
	ch := make(chan string)

	ch <- "Shakespeare"
}
```

Para receber do channel, fazemos:

```jsx
func main() {
	ch := make(chan string)

	// recebendo de um channel e descartando valor
	<-ch

	// recebendo de um channel e guardando numa variável
	name := <-ch
}
```

## Por que não funciona?

Se você rodar qualquer um dos códigos do exemplo anterior, vai acontecer isso:

```bash
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        main.go:5 +0x31
exit status 2
```

Isso acontece porque os channels por padrão vão pausar a execução da goroutine que tentar enviar ou receber. Ou seja, a goroutine que tentar enviar vai esperar até que outra esteja pronta para receber, e da mesma forma a goroutine que tentar receber vai esperar até que outra envie. 

No caso acima quando eu tento enviar para um channel, eu pauso a goroutine `main` para esperar que outra goroutine leia o que enviei, mas como não tem outra goroutine rodando, a `main` vai ficar esperando “para sempre” (`deadlock`).

O runtime do Go percebe que esse programa vai ficar parado indeterminadamente, porque todas as goroutines estão “dormindo” (`all goroutines are asleep`), então ele encerra o programa com um código de erro.

O mesmo acontece no segundo exemplo.

Também existem os *buffered channels*, que permitem a gente enviar para um canal uma quantidade X de dados antes que ele comece a bloquear a goroutine, mas vamos ver isso mais para frente.

## Usando channels corretamente

Para corrigir o problema do deadlock anterior, vamos criar outra goroutine que lê do channel, enquanto a goroutine `main` vai enviar para o channel.

```go
package main

import (
	"fmt"
)

func main() {
	ch := make(chan int)

	// obs. 1: 'go' vai fazer com que essa função seja executada numa 
	// nova goroutine, então ler do channel não vai bloquear a main

	go func() {
		// obs. 2: eu não preciso passar o ch como parâmetro da função
		// porque o ch está no da main escopo
		<-ch
	}()

	// a goroutine main vai esperar até que consiga enviar
	ch <- 12

	fmt.Println("Fim")
}
```

 

Se você está confuso com `ch <- 12` e `<-ch`, você pode pensar que a seta aponta para onde o dado está fluindo.

```go
// o dado está saindo do channel ch (receber) e sendo descartado
<-ch

// o dado está saindo do channel ch (receber) e sendo atribuído à variável x
x := <-ch

// o dado 12 está indo para o channel ch (enviar)
ch <- 12
```

## Conclusão

Isso conclui a parte 1 dessa série, com o conceito básico de channels.
Na parte 2 vamos ver um exemplo mais realístico de onde e como seria utilizado channels.