Channels por baixo dos panos
Tags: webdev, go, computerscience, beginners
Published: 2023-11-25 19:31:20

**_Channels_ são canais de comunicação e sincronização entre goroutines**. A comunicação é feita através do envio de dados entre _goroutines_ e a sincronização é feita pausando e retomando o processamento de uma _goroutine_ quando necessário.

Podemos criar uma nova _channel_, com o seguinte código:

```go
ch := make(chan Task, 3)
```

O tipo `chan Task` significa que estamos **criando uma channel que envia e recebe um dado de tipo _Task_**, enquanto **o parâmetro `3` representa a quantidade de dados que a channel pode guardar ao mesmo tempo** na sua fila, iremos explorar o que isso significa muito em breve.

Por debaixo dos panos **o valor da variável `ch` passa a ser um ponteiro para uma estrutura de dados chamada `hchan`**, que é nada mais do que uma _struct_ com 4 propriedades principais: buffer, sendx, recvx e lock. Cada propriedade possui um papel fundamental no funcionamento das channels.

O **_buffer_ é uma fila que guarda cópias dos dados enviados para a _channel_**. Quando uma _goroutine_ envia um dado para uma _channel_, o _buffer_ armazena e mantém um cópia desse dado - chamamos esse processo de **_enqueue_** - e, quando uma _goroutine_ recebe esse dado, ele é removido do _buffer_ e uma cópia fica com a _goroutine_ que o consome - esse processo chamamos de **_dequeue_**.

**_Sendx_ representa o índice do dado sendo enviado para a fila**. No nosso exemplo acima de uma fila com 3 elementos, se uma _goroutine_ envia o primeiro dado para a _channel_ o _sendx_ passa a ser 1, se envia mais outro o _sendx_ passa a ser 2, se envia mais outro o _sendx_ passa a ser...0. Essa é a definição de uma fila circular, pois o último elemento se conecta ao primeiro. O valor do _sendx_ seria zero porque o próximo elemento depois do terceiro é o primeiro, portanto se a fila está cheia - já possui 3 elementos - e eventualmente o primeiro elemento da fila for removido, quando uma _goroutine_ enviar para a _channel_ um novo dado, ele ficará guardado no índice 1 - e ai o valor do _sendx_ volta a ser 1 - e assim sucessivamente, de forma circular.

**_Recvx_ funciona da mesma forma que a propriedade _sendx_**, mas é referente ao índice do dado sendo recebido por uma _goroutine_.

**_Lock_ consiste em um _[mutex](https://go.dev/tour/concurrency/9)_**, que é um tipo primitivo de dado usado para evitar _race condition_ - ou seja, evitar que diferentes threads manipulem o mesmo dado compartilhado, no caso a _channel_, de foma simultânea, causando comportamentos inesperados ou corrupção de dados. Se duas _goroutines_ tentarem fazem operações simultâneas na mesma _channel_ apenas uma conseguirá o _lock_, e a outra deverá aguardar o _unlock_ da primeira para realizar sua própria operação. 

Podemos visualizar o fluxo de funcionamento de uma _channel_ enquanto fila circular com o diagram criado por [@akankshadokania](https://medium.com/@akankshadokania).

![Diagrama mostrando o funcionamento de uma channel](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdw7umbusuyxx46ijl8v.png)

Um ponto importante que pode ser levantado é: Já que a fila circular possui um tamanho fixo de quantidade de elementos que podem ser salvos ao mesmo tempo, o que acontece se uma goroutine enviar mais um dado para a channel que já está cheia? 

Usando nossa _channel_ de 3 elementos como exemplo, imaginando que ele já possui 3 _Tasks_ no _buffer_, o _sendx_ e _recvx_ seriam ambos 0 e o _runtime_ do Go, sabendo que o índice 0 do _buffer_ ainda está ocupado, **bloquearia a execução da _goroutine_ que tentou enviar o dado para a _channel_** e apenas desbloquearia quando o _recvx_ passasse a ser 1, ou seja, quando o primeiro elemento fosse removido da fila, assim permitindo o envio do dado para a _channel_.

Portanto _channels_ são uma ferramenta poderosa por possuir essas características interessantes: **São _goroutine-safe_**, ou seja, possuem a capacidade de lidar com múltiplas _goroutines_ sem levar a problemas de _race condition_; **Possuem capacidade de salvar dados**, como vimos no ponto sobre a propriedade _buffer_; Possui semântica **_FIFO - First In First Out -_,** ou seja, a fila do _buffer_ recebe e envia os dados em ordem; **Podem enviar dados** entre _goroutines_ e **podem bloquear/desbloquear** a execução de _goroutines_, dependendo da situação.