Um canal Go é um mecanismo de comunicação que permite que Goroutines troque dados. Quando os desenvolvedores têm vários Goroutines em execução ao mesmo tempo, os canais são a maneira mais conveniente de se comunicarem entre si.

Os desenvolvedores costumam usar esses canais para notificações e gerenciamento de simultaneidade em aplicativos.

Nesta postagem, vamos cobrir os usos gerais dos canais Go, incluindo como escrever e ler de um canal, como usar canais como parâmetros de função e como usar o intervalo para iterar sobre eles.

Criando uma estrutura de canal Go

Para começar, vamos criar um canal em Go usando a função make:

//por exemplo se o canal foi criado usando o seguinte: ch:=make (chan string)//esta é a estrutura básica dos canais tipo hchan struct {qcount uint//dados totais na fila dataqsiz uint//tamanho da fila circular buf unsafe.Pointer//ponteiro para uma matriz de elementos dataqsiz elementSize uint16 fechado uint32 sendx u int//enviar índice recvx uint//receber índice recvq waitq//lista de fila de recebimento sendq waitq//lista de fila de envio bloqueio mutex//bloqueio protege todos os campos em hchan, bem como vários}

Canais Go usa

Nesta seção, revisaremos os usos dos canais Go e como eles podem beneficiar o desenvolvimento de aplicativos.

Usando os canais Go como futuros e promessas

Os desenvolvedores costumam usar futuros e promessas em Go para solicitações e respostas. Por exemplo, se quisermos implementar um padrão assíncrono/esperar, devemos adicionar o seguinte:

package main import (“fmt””math/rand””time”) func longTimedOperation () -chan int32 {ch:=make (chan int32) func run () {adiar close (ch) time.Sleep (time.Second * 5) ch -rand.Int31n (300)} go run () return ch} func main () {ch:=longTimedOperation () fmt.Println (ch)}

Simplesmente simulando um processo de longa execução usando um atraso de 5 segundos, podemos enviar um valor inteiro aleatório para um canal, esperar pelo valor e recebê-lo.

Usando canais Go para notificações

Notificações são solicitações ou respostas únicas que retornam valores. Normalmente usamos um tipo de estrutura em branco como o tipo de elemento do canal de notificação devido ao tamanho de o tipo de estrutura em branco é zero, o que significa que os valores da estrutura não consomem memória.

Por exemplo, implementar uma notificação um para um com um canal recebe um valor de notificação:

pacote main import (“fmt””time”) type T=struct {} func main () {complete:=make (chan T) go func () {fmt.Println (“ping”) time.Sleep (time.Second * 5)//simulação de processo pesado -concluído//recebe um valor do canal concluído} concluído -struct {} {}//bloqueado esperando por uma notificação fmt.Println (“pong”)}

Isso nos permite usar um valor recebido de um canal para alertar outro Goroutine aguardando para enviar um valor para o mesmo canal.

Os canais também podem agendar notificações:

package main import (“fmt””time”) func scheduleNotification (t tempo.Duração) -chan struct {} {ch:=make (chan struct {}, 1) go func () {time.Sleep (t) ch -struct {} {}} () return ch} func main () {fmt.Println (“enviar primeiro”) -scheduleNotification (time.Second) fmt.Println (“secondly send”) -scheduleNotification (time.Second) fmt.Println (“lastly send”)}

Usando canais Go como semáforos de contagem

Para impor um número máximo de solicitações simultâneas, os desenvolvedores frequentemente usam semáforos de contagem para bloquear e desbloquear processos simultâneos para controlar recursos e aplicar exclusões mútuas. Por exemplo, os desenvolvedores podem controlar as operações de leitura e gravação em um banco de dados.

Existem duas maneiras de obter uma parte da propriedade de um semáforo de canal, semelhante a usar canais como mutexes:

Adquirir propriedade com um envio e liberação por meio de recebimento Tomando posse com um recebimento e liberação com um envio

No entanto, existem algumas regras específicas quando se possui um semáforo de canal. Primeiro, cada canal permite a troca de um tipo de dados específico, que também é chamado de tipo de elemento do canal.

Segundo, para um canal funcionar corretamente, alguém deve receber o que é enviado por meio do canal.

Por exemplo, podemos declarar um novo canal usando a palavra-chave chan, e podemos fechar um canal usando a função close (). Portanto, se bloquearmos o código usando a sintaxe -channel para ler do canal, uma vez concluído, podemos fechá-lo.

Finalmente, ao usar um canal como parâmetro de função, podemos especificar sua direção , o que significa especificar se o canal será usado para enviar ou receber.

Se soubermos a finalidade de um canal com antecedência, use esse recurso porque torna os programas mais robustos e seguros. Isso significa que não podemos enviar dados acidentalmente para um canal que apenas recebe dados ou receber dados de um canal que apenas envia dados.

Como resultado, se declararmos que um parâmetro de função de canal será usado para leitura apenas e tentamos escrever nele, recebemos uma mensagem de erro que provavelmente nos salvará de bugs desagradáveis.

Gravando em um canal Go

O código nesta subseção nos ensina como escrever para um canal em Go. Escrever o valor x no canal c é tão fácil quanto escrever c -x.

A seta mostra a direção do valor; não teremos problemas com esta instrução, desde que x e c tenham o mesmo tipo.

No código a seguir, a palavra-chave chan declara que o parâmetro da função c é um canal e deve ser seguido por o tipo de canal, que é int. Então, a instrução c -x nos permite escrever o valor x no canal c, e a função close () fecha o canal:

package main import (“fmt””time”) func writeToChannel (c chan int , x int) {fmt.Println (x) c -x close (c) fmt.Println (x)} func main () {c:=make (chan int) go writeToChannel (c, 10) time.Sleep ( 1 * time.Second)}

Finalmente, a execução do código anterior cria a seguinte saída:

$ go run writeCh.go 10

O estranho aqui é que a função writeToChannel () imprime o valor fornecido apenas uma vez, o que é causado quando a segunda instrução fmt.Println (x) nunca é executada.

A razão para isso é muito simples: a instrução c -x bloqueia a execução do resto do writeToChannel ( ) funciona porque ninguém está lendo o que foi escrito no canal c.

Portanto, quando a instrução time.Sleep (1 * time.Second) termina, o programa termina sem esperar por writeToChannel ().

A próxima seção ilustra s como ler dados de um canal.

Lendo de um canal Go

Podemos ler um único valor de um canal denominado c executando -c. Neste caso, a direção é do canal para o escopo externo:

package main import (“fmt””time”) func writeToChannel (c chan int, x int) {fmt.Println (“1”, x ) c -x close (c) fmt.Println (“2”, x)} func main () {c:=make (chan int) go writeToChannel (c, 10) time.Sleep (1 * time.Segundo) fmt.Println (“Read:”, -c) time.Sleep (1 * time.Segundo) _, ok:=-c if ok {fmt.Println (“Canal está aberto!”)} else {fmt. Println (“O canal está fechado!”)}}

A implementação da função writeToChannel () é a mesma de antes. No código anterior, lemos do canal c usando a notação -c.

A segunda instrução time.Sleep (1 * time.Second) nos dá o tempo para ler do canal.

O código Go atual funciona bem quando o canal é fechado; no entanto, se o canal estivesse aberto, o código Go apresentado aqui teria descartado o valor lido do canal porque usamos o caractere _ na instrução _, ok:=-c instrução.

Use uma instrução adequada nome da variável em vez de _ se também quisermos armazenar o valor encontrado no canal, caso esteja aberto.

Executar readCh.go gera a seguinte saída:

$ go run readCh.go 1 10 Ler: 10 2 10 Canal fechado! $ go run readCh.go 1 10 2 10 Read: 10 Channel is closed!

Embora a saída ainda não seja determinística, ambas as instruções fmt.Println (x) da função writeToChannel () são executadas porque o canal é desbloqueado quando lemos dele.

Recebendo de um canal fechado

Nesta subseção, revisaremos o que acontece quando tentamos ler de um canal fechado usando o código Go encontrado em readClose.go.

Nesta parte do readClose.go programa, devemos criar um novo canal interno denominado willClose para gravar dados nele, ler os dados e fechar o canal após receber os dados:

package main import (“fmt”) func main () {willClose:=make (chan int, 10) willClose –1 willClose -0 willClose -2 -willClose -willClose -willClose close (willClose) read:=-willClose fmt.Println (read)}

Executando o código anterior (salvo no arquivo readClose.go) gera a seguinte saída:

$ go run readClose.go 0

Isso significa que a leitura de um canal fechado retorna o valor zero de seu tipo de dados, que em este caso é 0.

Canais como parâmetros de função

Embora não tenhamos usado parâmetros de função ao trabalhar com readCh.go ou writeCh.go, Go nos permite especificar a direção de um canal ao usá-lo como um parâmetro de função, ou seja, se ele é usado para leitura ou gravação.

Esses dois tipos de canais são chamados de canais unidirecionais, enquanto os canais são bidirecionais por padrão.

Examine o código Go das duas funções a seguir:

func f1 (c chan int, x int) {fmt.Println (x) c -x} func f2 (c chan -int, x int) {fmt.Println (x) c -x}

Embora ambas as funções implementem a mesma funcionalidade, suas definições são ligeiramente diferentes. A diferença é criada pelo símbolo -encontrado à direita da palavra-chave chan na definição da função f2 ().

Isso denota que o canal c só pode escrever. Se o código de uma função Go tentar ler de um parâmetro de canal somente gravação (também conhecido como canal somente envio), o compilador Go gerará a seguinte mensagem de erro:

# command-line-arguments a.go: 19: 11: operação inválida: intervalo de entrada (receber apenas envio de tipo chan -int)

Da mesma forma, podemos ter as seguintes definições de função:

func f1 (out chan -int64, in -chan int64) {fmt.Println (x) c -x} func f2 (out chan int64, in chan int64) {fmt.Println (x) c -x}

A definição de f2 () combina um canal somente leitura nomeado com um canal somente gravação denominado out. Se acidentalmente tentarmos escrever e fechar um parâmetro de canal somente leitura (também conhecido como canal somente recebimento) de uma função, obteremos a seguinte mensagem de erro:

# command-line-arguments a.go: 13: 7: operação inválida: out -i (enviar para receber apenas tipo -chan int) a.go: 15: 7: operação inválida: fechar (out) (não é possível fechar canal somente de recepção)

Intervalo acima Canais Go

Podemos usar a sintaxe de intervalo no Golang para iterar em um canal para ler seus valores. A iteração aqui aplica o conceito primeiro a entrar, primeiro a sair (FIFO): enquanto adicionarmos dados ao buffer do canal, podemos ler do buffer como uma fila:

package main import”fmt”func main ( ) {ch:=make (chan string, 2) ch -“um”ch -“dois”close (ch) para elem:=range ch {fmt.Println (elem)}}

Como mencionado acima, usar intervalo para iterar de um canal aplica o princípio FIFO (leitura de uma fila). Portanto, a execução do código anterior resulta no seguinte:

$ go run range-over-channels.go one two

Conclusão

Go são usados ​​para comunicação entre funções em execução simultânea, enviando e recebendo dados de um tipo de elemento específico. Quando temos vários Goroutines em execução ao mesmo tempo, os canais são a maneira mais conveniente para eles se comunicarem.

Obrigado pela leitura e feliz programação!