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.
Desafio
FácilVamos 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
Itemcom os camposID(int) eValue(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 usandolen(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
cherrySua 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 - cherryObserve 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 buffercap(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
}
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
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool