Menu
Coddy logo textTech

Channels con buffer vs. sin buffer

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 61 de 107.

Los canales que hemos utilizado hasta ahora son sin búfer: no tienen capacidad para almacenar valores. Una operación de envío se bloquea hasta que otra goroutine recibe, y viceversa. Los canales con búfer añaden almacenamiento interno, lo que permite que los envíos se completen sin un receptor inmediato.

Crea un canal con búfer pasando una capacidad a make:

// Sin búfer - capacidad 0
unbuffered := make(chan int)

// Con búfer - capacidad 3
buffered := make(chan int, 3)

Con un canal con búfer, los envíos solo se bloquean cuando el búfer está lleno, y las recepciones solo se bloquean cuando el búfer está vacío:

ch := make(chan string, 2)

ch <- "first"   // no bloquea - el búfer tiene espacio
ch <- "second"  // no bloquea - el búfer tiene espacio
// ch <- "third" bloquearía - el búfer está lleno

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

Utiliza len(ch) para comprobar cuántos elementos hay actualmente en el búfer, y cap(ch) para la capacidad total.

Cuándo usar cada uno: Los canales sin búfer proporcionan una sincronización estrecha: el emisor sabe que el receptor recibió el valor. Los canales con búfer desacoplan la sincronización del emisor y el receptor, lo cual es útil cuando los productores y consumidores trabajan a diferentes velocidades. Sin embargo, los canales con búfer pueden ocultar errores de sincronización, por lo que es preferible usar canales sin búfer a menos que se tenga una razón específica para usar el búfer.

challenge icon

Desafío

Fácil

Vamos a construir un procesador por lotes que demuestre la diferencia entre los canales con búfer (buffered) y sin búfer (unbuffered). Crearás un sistema donde un productor envía elementos a un procesador, utilizando canales con búfer para desacoplar sus tiempos y permitir que el productor trabaje por adelantado.

Organizarás tu código en dos archivos:

  • processor.go: Define tu lógica de procesamiento por lotes utilizando canales.

    Crea una estructura Item con los campos ID (int) y Value (string).

    Implementa dos funciones:

    • Producer(items []Item, out chan Item) - Envía cada elemento al canal de salida. Después de enviar cada elemento, imprime: Produced item [ID]. Después de que se hayan enviado todos los elementos, imprime el número actual de elementos esperando en el búfer usando len(out) en el formato: Buffer has [count] items. Luego cierra el canal.
    • Consumer(in chan Item) []string - Recibe elementos del canal de entrada y los recolecta en un slice de cadenas formateadas. Para cada elemento recibido, la cadena debe ser: Consumed: [ID] - [Value]. Devuelve el slice cuando el canal se cierre.
  • main.go: Configura el canal con búfer y coordina al productor y al consumidor.

    Lee la capacidad del búfer, luego el número de elementos, seguido del ID y el valor de cada elemento. Crea un canal con búfer con la capacidad especificada. Ejecuta primero el Producer (no como una goroutine) para llenar el búfer, luego ejecuta el Consumer para procesar todos los elementos. Imprime cada resultado consumido en una línea separada.

Se proporcionarán las siguientes entradas:

  • Línea 1: Capacidad del búfer (entero)
  • Línea 2: Número de elementos (entero)
  • Líneas siguientes: Para cada elemento, dos líneas - el ID del elemento (entero), luego su valor (cadena)

Por ejemplo, dado:

3
3
1
apple
2
banana
3
cherry

Tu salida debería ser:

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

Observa cómo con una capacidad de búfer de 3, el productor puede enviar los 3 elementos sin bloquearse, y el búfer muestra 3 elementos esperando antes de que comience el consumidor. Si el búfer fuera más pequeño que el número de elementos, el productor se bloquearía esperando espacio.

Hoja de referencia

Los canales en Go pueden ser sin búfer (capacidad 0) o con búfer (con almacenamiento interno).

Crea canales con make:

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

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

Comportamiento de los canales con búfer:

  • Los envíos se bloquean solo cuando el búfer está lleno
  • Las recepciones se bloquean solo cuando el búfer está vacío
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"

Comprobar el estado del búfer:

  • len(ch) - número de elementos actualmente en el búfer
  • cap(ch) - capacidad total del búfer

Cuándo usar cada uno:

  • Sin búfer: Sincronización estricta—el emisor sabe que el receptor recibió el valor
  • Con búfer: Desacoplar el tiempo del emisor y el receptor cuando trabajan a diferentes velocidades

Pruébalo tú mismo

package main

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

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

	// Leer la capacidad del búfer
	line, _ := reader.ReadString('\n')
	bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))

	// Leer el número de elementos
	line, _ = reader.ReadString('\n')
	numItems, _ := strconv.Atoi(strings.TrimSpace(line))

	// Leer el ID y el valor de cada elemento
	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: Crear un canal con búfer con la capacidad especificada

	// TODO: Ejecutar el Productor (no como una goroutine) para llenar el búfer

	// TODO: Ejecutar el Consumidor para procesar todos los elementos

	// TODO: Imprimir cada resultado consumido en una línea separada
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos