No gerador uma gorrotina escrevia no canal e a main lia.

Agora os papéis se invertem. Quem lê é a gorrotina.

O que é um trabalhador Link para o cabeçalho

Um trabalhador é uma gorrotina que recebe valores de um canal e os processa até o canal ser fechado.

Ele também aparece como consumidor ou sink, mas esse segundo nome só vale quando ele é o último estágio, ou seja, quando não repassa nada adiante.

Vamos à primeira tentativa. A main envia os inteiros de 0 a 9 e o trabalhador imprime cada um:

package main

import "fmt"

func trabalhador(entrada <-chan int) {
	for valor := range entrada {
		fmt.Printf("valor: %v\n", valor)
	}
}

func main() {
	entrada := make(chan int)
	go trabalhador(entrada)
	for i := range 10 {
		entrada <- i
	}
	// fechar a entrada encerra o range do trabalhador
	close(entrada)
}

Repare no for valor := range entrada. O range recebe um valor por vez e termina sozinho quando quem envia fecha o canal, então o trabalhador não precisa de contador nem de valor especial para saber que acabou.

O fmt.Printf está ali só para mostrar a execução. No lugar dele entra o que você precisar processar.

$ go run .
valor: 0
valor: 1
...
valor: 8

Opa! Cadê o 9?

Rodei 200 vezes aqui e o valor: 9 sumiu em 189.

Mas o close não resolve? Link para o cabeçalho

Porém fechar a entrada não espera o trabalhador terminar de processar.

Quando o último envio retorna, o trabalhador recebeu o 9, mas talvez ainda não o tenha impresso. A main fecha o canal, termina, e como já vimos no olá mundo, leva junto qualquer gorrotina que ainda esteja rodando.

E o time.Sleep continua sendo chute, porque nenhum tempo fixo garante que o trabalho acabou.

O que falta é o trabalhador avisar que terminou:

 func main() {
 	entrada := make(chan int)
-	go trabalhador(entrada)
+	pronto := make(chan struct{})
+	go func() {
+		trabalhador(entrada)
+		// fechar o canal avisa "terminei" a qualquer número de leitores
+		close(pronto)
+	}()
 	for i := range 10 {
 		entrada <- i
 	}
 	// fechar a entrada encerra o range do trabalhador
 	close(entrada)
+	<-pronto
 }

Agora a main fica parada em <-pronto até o trabalhador sair do laço, e o 9 aparece em todas as execuções.

Mas por que fechar em vez de enviar? Link para o cabeçalho

Repare que para avisar eu fechei o canal pronto em vez de enviar um valor nele. Por quê?

Fechar um canal é o jeito usual em Go de comunicar algo que acontece uma vez só, como “terminei” ou “pode parar”. O Go libera de uma vez todo mundo que estiver lendo o canal, então o aviso funciona para qualquer número de leitores.

Por isso o canal é um chan struct{}, que não carrega dado nenhum. Ninguém vai ler um valor dali, só esperar o fechamento.

Um último detalhe que chamo atenção é quem fecha o pronto: a gorrotina anônima lá na main. O trabalhador só processa valores e nem sabe que esse aviso existe.

De onde veio o exemplo Link para o cabeçalho

O código está na pasta trabalhador do concorrencia-go, junto com o teste que confere os dez valores.

No próximo vem o pipeline, que junta os dois papéis: cada estágio é trabalhador de quem vem antes e gerador para quem vem depois.

Lembrando que o for i := range 10 só funciona a partir do Go 1.22.

Então é isso, pessoal!

Até a próxima!

{}’s