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.
Hoja de referencia
El paquete sync proporciona mutexes para proteger datos compartidos cuando múltiples goroutines necesitan acceso directo al mismo recurso.
sync.Mutex
Un sync.Mutex proporciona acceso exclusivo utilizando Lock() y 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 con Unlock() asegura que el bloqueo se libere incluso si la función retorna anticipadamente o entra en pánico.
sync.RWMutex
Un sync.RWMutex permite múltiples lectores concurrentes pero acceso exclusivo para escritores:
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
}Utilice RLock()/RUnlock() para operaciones de lectura y Lock()/Unlock() para operaciones de escritura.
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