Menu
Coddy logo textTech

Canais com Buffer vs. Sem Buffer

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 61 de 107.

Os canais que usamos até agora são não bufferizados—eles não têm capacidade para reter valores. Uma operação de envio bloqueia até que outra goroutine receba, e vice-versa. Canais bufferizados adicionam armazenamento interno, permitindo que os envios sejam concluídos sem um receptor imediato.

Crie um canal com buffer passando uma capacidade para make:

// Sem buffer - capacidade 0
unbuffered := make(chan int)

// Com buffer - capacidade 3
buffered := make(chan int, 3)

Com um canal com buffer, os envios bloqueiam apenas quando o buffer está cheio, e os recebimentos bloqueiam apenas quando o buffer está vazio:

ch := make(chan string, 2)

ch <- "first"   // não bloqueia - o buffer tem espaço
ch <- "second"  // não bloqueia - o buffer tem espaço
// ch <- "third" bloquearia - o buffer está cheio

fmt.Println(<-ch)  // "first"
fmt.Println(<-ch)  // "second"

Use len(ch) para verificar quantos itens estão atualmente no buffer, e cap(ch) para a capacidade total.

Quando usar cada um: Canais não bufferizados fornecem sincronização estreita — o remetente sabe que o receptor recebeu o valor. Canais bufferizados desacoplam o tempo do remetente e do receptor, o que é útil quando produtores e consumidores trabalham em velocidades diferentes. No entanto, canais bufferizados podem mascarar bugs de sincronização, portanto, prefira os não bufferizados, a menos que você tenha um motivo específico para o buffering.

challenge icon

Desafio

Fácil

Vamos construir um processador em lote que demonstra a diferença entre canais com buffer (buffered) e sem buffer (unbuffered). Você criará um sistema onde um produtor envia itens para um processador, usando canais com buffer para desacoplar o tempo de execução e permitir que o produtor trabalhe antecipadamente.

Você organizará seu código em dois arquivos:

  • processor.go: Defina sua lógica de processamento em lote usando canais.

    Crie uma struct Item com os campos ID (int) e Value (string).

    Implemente duas funções:

    • Producer(items []Item, out chan Item) - Envia cada item para o canal de saída. Após enviar cada item, imprima: Produced item [ID]. Depois que todos os itens forem enviados, imprima o número atual de itens aguardando no buffer usando len(out) no formato: Buffer has [count] items. Em seguida, feche o canal.
    • Consumer(in chan Item) []string - Recebe itens do canal de entrada e os coleta em um slice de strings formatadas. Para cada item recebido, a string deve ser: Consumed: [ID] - [Value]. Retorne o slice quando o canal for fechado.
  • main.go: Configure o canal com buffer e coordene o produtor e o consumidor.

    Leia a capacidade do buffer, depois o número de itens, seguido pelo ID e valor de cada item. Crie um canal com buffer com a capacidade especificada. Execute o Producer primeiro (não como uma goroutine) para preencher o buffer e, em seguida, execute o Consumer para processar todos os itens. Imprima cada resultado consumido em uma linha separada.

As seguintes entradas serão fornecidas:

  • Linha 1: Capacidade do buffer (inteiro)
  • Linha 2: Número de itens (inteiro)
  • Linhas seguintes: Para cada item, duas linhas - o ID do item (inteiro) e, em seguida, seu valor (string)

Por exemplo, dado:

3
3
1
apple
2
banana
3
cherry

Sua saída deve ser:

Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherry

Observe como, com uma capacidade de buffer de 3, o produtor pode enviar todos os 3 itens sem bloquear, e o buffer mostra 3 itens aguardando antes do consumidor começar. Se o buffer fosse menor que o número de itens, o produtor bloquearia aguardando por espaço.

Folha de consulta

Canais em Go podem ser não-bufferizados (capacidade 0) ou bufferizados (com armazenamento interno).

Crie canais com make:

// Unbuffered - capacity 0
unbuffered := make(chan int)

// Buffered - capacity 3
buffered := make(chan int, 3)

Comportamento de canais bufferizados:

  • Envios bloqueiam apenas quando o buffer está cheio
  • Recebimentos bloqueiam apenas quando o buffer está vazio
ch := make(chan string, 2)

ch <- "first"   // doesn't block - buffer has space
ch <- "second"  // doesn't block - buffer has space
// ch <- "third" would block - buffer is full

fmt.Println(<-ch)  // "first"
fmt.Println(<-ch)  // "second"

Verificar o status do buffer:

  • len(ch) - número de itens atualmente no buffer
  • cap(ch) - capacidade total do buffer

Quando usar:

  • Não-bufferizado: Sincronização rígida — o remetente sabe que o receptor recebeu o valor
  • Bufferizado: Desacoplar o tempo do remetente e do receptor quando eles trabalham em velocidades diferentes

Experimente você mesmo

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Ler a capacidade do buffer
	line, _ := reader.ReadString('\n')
	bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))

	// Ler o número de itens
	line, _ = reader.ReadString('\n')
	numItems, _ := strconv.Atoi(strings.TrimSpace(line))

	// Ler o ID e o valor de cada item
	items := make([]Item, numItems)
	for i := 0; i < numItems; i++ {
		line, _ = reader.ReadString('\n')
		id, _ := strconv.Atoi(strings.TrimSpace(line))
		line, _ = reader.ReadString('\n')
		value := strings.TrimSpace(line)
		items[i] = Item{ID: id, Value: value}
	}

	// TODO: Criar um channel com buffer com a capacidade especificada

	// TODO: Executar o Producer (não como uma goroutine) para preencher o buffer

	// TODO: Executar o Consumer para processar todos os itens

	// TODO: Imprimir cada resultado consumido em uma linha separada
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos