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.
Desafío
FácilVamos 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
Itemcon los camposID(int) yValue(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 usandolen(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
cherryTu 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 - cherryObserva 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úfercap(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
}
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
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool