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.
Aunque los canales son la forma preferida de Go para coordinar goroutines, a veces necesitas proteger directamente los datos compartidos. 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
}Usar defer c.mu.Unlock() garantiza que el bloqueo se libere incluso si la función devuelve el control antes de tiempo o entra en pánico: un patrón común y recomendado.
Cuando las lecturas son frecuentes pero las escrituras son poco frecuentes, sync.RWMutex ofrece un mejor rendimiento. Permite varios 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
}Usa RLock()/RUnlock() para las operaciones de lectura y Lock()/Unlock() para las escrituras. Esto permite lecturas simultáneas mientras garantiza que las escrituras sean seguras.
Desafío
FácilConstruyamos un sistema de inventario seguro para subprocesos que realice un seguimiento de los niveles de existencias de los productos. Tu sistema gestionará de forma segura las lecturas y escrituras simultáneas mediante mutexes, garantizando la integridad de los datos cuando varias operaciones ocurran al mismo tiempo.
Organizarás tu código en dos archivos:
inventory.go: Define tu sistema de gestión de inventario seguro para subprocesos.Crea una estructura
Inventoryque almacene las cantidades de los productos en un mapa y use unsync.RWMutexpara proteger el acceso. Tu inventario debe admitir estas operaciones:NewInventory() *Inventory- Crea un inventario nuevo con un mapa inicializadoAddStock(product string, quantity int)- Añade una cantidad a las existencias de un producto (usa un bloqueo exclusivo, ya que esto modifica los datos)GetStock(product string) int- Devuelve las existencias actuales 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- Retira una cantidad de las existencias si hay existencias suficientes. Devuelvetruesi tiene éxito,falsesi no hay existencias suficientes (usa un bloqueo exclusivo)
Recuerda usar
deferpara desbloquear y garantizar que los bloqueos siempre se liberen correctamente.main.go: Lee las operaciones y muestra el funcionamiento de tu inventario seguro para subprocesos.Lee el número de operaciones y, después, procesa cada operación. Cada operación tiene un tipo (
add,getoremove), un nombre de producto y, para las operacionesadd/remove, una cantidad.Para cada operación, muestra el resultado:
add: MuestraAdded [quantity] [product]get: Muestra[product]: [stock] in stockremove: MuestraRemoved [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,getoremove) - Nombre del producto
- Cantidad (solo para las operaciones
addyremove)
- Tipo de operación (
Por ejemplo, dada la siguiente entrada:
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 varias 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 la cantidad, añadir stock, imprimir el resultado
case "get":
// TODO: Obtener el stock, imprimir el resultado
case "remove":
// TODO: Leer la 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 PoolPractica por tu cuenta: Compilador de Go online