Aula 33 - Tutorial Golang - Iteração sobre Valores Recebidos de um Canal
Introdução:
Nesta aula, vamos explorar um conceito importante em programação concorrente em Go: a iteração sobre valores recebidos de um canal.
Veremos como a sintaxe do
for e
range pode ser utilizada para percorrer os valores recebidos de um canal e como o fechamento do canal afeta essa iteração.
package main
import "fmt"
func main() {
// Criando um canal com capacidade para 2 valores
// Iremos iterar sobre 2 valores no canal `queue`
queue := make(chan string, 2)
queue <- "one"
queue <- "two"
close(queue)
// Iterando sobre os valores recebidos do canal usando `for` e `range`
for elem := range queue {
fmt.Println(elem)
}
}
A função main é a entrada principal do programa
Nela criamos um canal chamado
queue com
capacidade para
2 valores usando
make(chan string, 2).
Em seguida, enviamos dois valores para o canal
queue usando o operador
<-.
Fechamos o canal
queue usando a função
close(queue).
Isso indica que nenhum valor adicional será enviado para o canal.
Usamos um loop
for e
range para
iterar sobre os
valores recebidos do
canal queue.
O loop continua até que o canal seja fechado e todos os valores sejam recebidos.
Em cada iteração, o valor recebido é armazenado na variável
elem, e então o valor é impresso no console usando
fmt.Println(elem).
O resultado será a impressão dos valores "
one" e "
two" que foram enviados e recebidos do canal
queue.
Esse exemplo ilustra como utilizar o
for e
range para iterar sobre os valores recebidos de um canal em Go.
É uma forma eficiente de processar os valores recebidos do canal de forma sequencial.
Processamento Paralelo
Caso de uso
Ao usar
goroutines para processamento paralelo, é comum utilizar canais para enviar e receber dados entre as goroutines.
A iteração sobre os valores recebidos de um canal permite processar os resultados simultaneamente.
Import do sync
Por causa do import do
sync, antes de rodar o código precisamos executar:
go mod init <modulename>, para limpar e atualizar o arquivo
go.mod e o arquivo
go.sum.
Ele remove as dependências que não estão mais sendo utilizadas no projeto e adiciona as dependências necessárias com as versões corretas.
É útil para garantir que as dependências do projeto estejam atualizadas e corretas.
No meu caso, vou chamar o módulo de
fluentcode.com.
go mod init fluentcode.com
Rode também o:
go mod tidy para inicializar um módulo e gerenciar as dependências do projeto.
go mod tidy
Exemplo de código:
package main
import (
"fmt"
"sync"
)
func processWorker(id int, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
// Realize o processamento do trabalho aqui
}
}
func main() {
jobs := make(chan int, 5)
var wg sync.WaitGroup
numWorkers := 3
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go processWorker(i, jobs, &wg)
}
// Enviar trabalhos para os workers
for i := 1; i <= 10; i++ {
jobs <- i
}
close(jobs)
// Aguardar a conclusão de todos os workers
wg.Wait()
}
Neste exemplo, criamos
goroutines (
workers) que processam trabalhos em paralelo.
Os trabalhos são enviados para o
canal jobs e cada
worker itera sobre os valores recebidos desse canal usando o
for e
range.
A saída do código será algo semelhante a:
Worker 1 processing job 1
Worker 2 processing job 2
Worker 3 processing job 3
Worker 1 processing job 4
Worker 2 processing job 5
Worker 3 processing job 6
Worker 1 processing job 7
Worker 2 processing job 8
Worker 3 processing job 9
Worker 1 processing job 10
No entanto, é importante ressaltar que a ordem exata pode variar a cada execução do programa devido à concorrência dos workers.
Portanto, embora o primeiro worker comece a processar o job 1, não é garantido que ele terminará de processá-lo antes que outros workers iniciem seus próprios jobs.
Além disso, como o canal jobs tem uma capacidade de 5, o envio de jobs além desse limite ficará em espera até que os workers processem alguns jobs existentes e liberem espaço no canal.
Portanto, a ordem precisa de processamento e a distribuição exata dos jobs entre os workers podem variar a cada execução.
Função processWorker() do código acima
A função
processWorker() representa a
rotina de trabalho de cada
worker.
Recebe o
ID do worker, um canal
jobs de onde ele recebe os trabalhos a serem processados e um
ponteiro para um
objeto sync.WaitGroup para indicar que o worker concluiu seu trabalho.
O loop:
for job := range jobs, percorre o canal
jobs e recebe os trabalhos à medida que são enviados.
Em seguida, o worker processa o trabalho, que é simulado com a impressão de uma mensagem.
O
defer wg.Done() é usado para indicar que o
worker terminou seu trabalho, decrementando o contador do
sync.WaitGroup quando a função
processWorker retorna.
Função main() do código acima
A função
main() é responsável por
configurar e
coordenar os
workers.
Criamos um
canal jobs com uma
capacidade de
5 para armazenar os trabalhos a serem processados.
Inicializamos uma variável
wg do tipo
sync.WaitGroup para sincronizar a finalização de todos os workers.
Definimos o número de workers desejado (
numWorkers = 3) e, em seguida, iniciamos
goroutines chamando a função
processWorker() para cada
worker.
Em seguida, enviamos
10 trabalhos para o canal
jobs utilizando um
loop for.
Fechamos o canal
jobs para indicar que nenhum trabalho adicional será enviado.
Por fim, chamamos
wg.Wait() para aguardar a conclusão de todos os workers, o que é indicado pelo
wg.Done() em cada worker.
Consumo de Streams de Dados
Caso de uso
Em situações em que os dados estão sendo transmitidos continuamente em um canal, a iteração sobre os valores recebidos permite consumir esses dados conforme eles estão disponíveis, em tempo real.
Exemplo de código:
package main
import (
"fmt"
"time"
)
func dataProducer(data chan<- int) {
for i := 1; i <= 5; i++ {
data <- i
time.Sleep(time.Second)
}
close(data)
}
func main() {
data := make(chan int)
go dataProducer(data)
for value := range data {
fmt.Println("Received:", value)
}
}
Neste exemplo, temos uma
goroutine dataProducer que envia valores para o canal data em intervalos de
1 segundo.
A função
main() itera sobre os valores recebidos desse
canal usando o
for e
range, imprimindo os valores à medida que são recebidos.
Esses são apenas dois casos de uso comuns em que a iteração sobre valores recebidos de um canal é aplicável.
A versatilidade dessa abordagem permite seu uso em uma ampla gama de cenários de programação concorrente.
Conclusão
Dominar a iteração sobre valores recebidos de um canal é essencial para o desenvolvimento de programas concorrentes eficientes em Go.
Ao compreender os conceitos abordados nesta aula, você estará um passo mais próximo de aproveitar todos os benefícios da programação concorrente em Go.
Continue praticando e explorando os recursos dessa poderosa linguagem!
#Golang #ProgramacaoConcorrente #IteracaoCanal #ForRange
Eu fico por aqui e agradeço a você pela audiência.
Até mais. :)
Meus links de afiliados:
Obrigado e bons estudos. ;)