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 usado hasta ahora no tienen 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 buffer tiene espacio
ch <- "second" // no bloquea - el buffer tiene espacio
// ch <- "third" bloquearía - el buffer está lleno
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Usa 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 remitente sabe que el receptor recibió el valor. Los canales con búfer desacoplan los tiempos del remitente y del receptor, lo que resulta útil cuando los productores y los consumidores trabajan a distintas velocidades. Sin embargo, los canales con búfer pueden ocultar errores de sincronización, así que prefiere los canales sin búfer a menos que tengas una razón específica para usar búferes.
Desafío
FácilConstruyamos un procesador por lotes que demuestre la diferencia entre los canales buffered y no buffered. Crearás un sistema en el que un productor envía items a un procesador, utilizando canales buffered para desacoplar sus tiempos y permitir que el productor se adelante.
Organizarás tu código en dos archivos:
processor.go: Define la lógica de procesamiento por lotes utilizando canales.Crea una estructura
Itemcon camposID(int) yValue(string).Implementa dos funciones:
Producer(items []Item, out chan Item)- Envía cada item al canal de salida. Después de enviar cada item, imprime:Produced item [ID]. Después de enviar todos los items, imprime el número actual de items que esperan en el buffer utilizandolen(out)con el formato:Buffer has [count] items. Después, cierra el canal.Consumer(in chan Item) []string- Recibe items del canal de entrada y los recopila en un slice de cadenas formateadas. Para cada item recibido, la cadena debe ser:Consumed: [ID] - [Value]. Devuelve el slice cuando el canal se cierra.
main.go: Configura el canal buffered y coordina el productor y el consumidor.Lee la capacidad del buffer, después el number de items, seguido del ID y el valor de cada item. Crea un canal buffered con la capacidad especificada. Ejecuta primero Producer (no como una goroutine) para llenar el buffer y, después, ejecuta Consumer para procesar todos los items. Imprime cada resultado consumed en una línea separada.
Se proporcionarán las siguientes entradas:
- Línea 1: Capacidad del buffer (entero)
- Línea 2: Number de items (entero)
- Líneas siguientes: Para cada item, dos líneas: el ID del item (entero), después su valor (cadena)
Por ejemplo, dada la siguiente entrada:
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 que, con una capacidad del buffer de 3, el productor puede enviar los 3 items sin bloquearse, y el buffer muestra 3 items esperando antes de que comience el consumidor. Si el buffer fuera más pequeño que el number de items, el productor se bloquearía esperando espacio.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer la capacidad del buffer
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 buffer con la capacidad especificada
// TODO: Ejecutar el Producer (no como una goroutine) para llenar el buffer
// TODO: Ejecutar el Consumer 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 PoolPractica por tu cuenta: Compilador de Go online