sync.Mutex y sync.RWMutex
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 63 de 107.
Si bien los canales son la forma preferida de Go para coordinar goroutines, a veces es necesario proteger los datos compartidos directamente. El paquete sync proporciona mutexes (bloqueos) que garantizan que solo una goroutine acceda a un recurso a la vez.
Un sync.Mutex tiene dos métodos: Lock() y Unlock(). Cuando una goroutine llama a Lock(), obtiene acceso exclusivo. Otras goroutines que llamen a Lock() se bloquearán hasta que se llame a Unlock():
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}El uso de defer c.mu.Unlock() garantiza que el bloqueo se libere incluso si la función retorna anticipadamente o entra en pánico, un patrón común y recomendado.
Cuando las lecturas son frecuentes pero las escrituras son raras, sync.RWMutex ofrece un mejor rendimiento. Permite múltiples lectores simultáneamente, pero los escritores obtienen acceso exclusivo:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // se permiten múltiples lectores
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // acceso exclusivo para escritura
defer c.mu.Unlock()
c.data[key] = value
}Utiliza RLock()/RUnlock() para operaciones de lectura y Lock()/Unlock() para escrituras. Esto permite lecturas concurrentes mientras se asegura que las escrituras sean seguras.
Desafío
FácilVamos a construir un sistema de inventario seguro para hilos (thread-safe) que rastree los niveles de stock de los productos. Tu sistema manejará lecturas y escrituras concurrentes de forma segura utilizando mutexes, garantizando la integridad de los datos cuando ocurran múltiples operaciones simultáneamente.
Organizarás tu código en dos archivos:
inventory.go: Define tu sistema de gestión de inventario thread-safe.Crea un struct
Inventoryque almacene las cantidades de productos en un mapa y utilice unsync.RWMutexpara proteger el acceso. Tu inventario debe admitir estas operaciones:NewInventory() *Inventory- Crea un nuevo inventario con un mapa inicializadoAddStock(product string, quantity int)- Agrega cantidad al stock de un producto (usa un bloqueo exclusivo ya que esto modifica los datos)GetStock(product string) int- Devuelve el stock actual de un producto, o 0 si no se encuentra (usa un bloqueo de lectura ya que esto solo lee datos)RemoveStock(product string, quantity int) bool- Elimina cantidad del stock si existe suficiente stock. Devuelvetruesi tiene éxito,falsesi el stock es insuficiente (usa un bloqueo exclusivo)
Recuerda usar
deferpara desbloquear y asegurar que los bloqueos siempre se liberen correctamente.main.go: Lee las operaciones y demuestra tu inventario thread-safe.Lee el número de operaciones y luego procesa cada una. Cada operación tiene un tipo (
add,get, oremove), un nombre de producto y, para las operacionesadd/remove, una cantidad.Para cada operación, imprime el resultado:
add: ImprimeAdded [quantity] [product]get: Imprime[product]: [stock] in stockremove: ImprimeRemoved [quantity] [product]si tiene éxito, oInsufficient stock for [product]si no
Se proporcionarán las siguientes entradas:
- Línea 1: Número de operaciones (entero)
- Líneas siguientes: Para cada operación:
- Tipo de operación (
add,get, oremove) - Nombre del producto
- Cantidad (solo para operaciones
addyremove)
- Tipo de operación (
Por ejemplo, dado:
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
applesTu salida debería ser:
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockEl RWMutex permite que múltiples llamadas a GetStock lean simultáneamente, mientras que AddStock y RemoveStock obtienen acceso exclusivo al modificar el inventario.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el número de operaciones
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Crear un nuevo inventario
inventory := NewInventory()
// Procesar cada operación
for i := 0; i < numOps; i++ {
// Leer el tipo de operación
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
// Leer el nombre del producto
productLine, _ := reader.ReadString('\n')
product := strings.TrimSpace(productLine)
// TODO: Manejar cada tipo de operación (add, get, remove)
// Para "add" y "remove", leer la cantidad de la entrada
// Llamar al método de inventario apropiado
// Imprimir el resultado según la descripción del desafío
switch opType {
case "add":
// TODO: Leer cantidad, añadir stock, imprimir resultado
case "get":
// TODO: Obtener stock, imprimir resultado
case "remove":
// TODO: Leer cantidad, eliminar stock, imprimir el resultado apropiado
}
}
}
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