Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 Inventory que almacene las cantidades de productos en un mapa y utilice un sync.RWMutex para proteger el acceso. Tu inventario debe admitir estas operaciones:

    • NewInventory() *Inventory - Crea un nuevo inventario con un mapa inicializado
    • AddStock(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. Devuelve true si tiene éxito, false si el stock es insuficiente (usa un bloqueo exclusivo)

    Recuerda usar defer para 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, o remove), un nombre de producto y, para las operaciones add/remove, una cantidad.

    Para cada operación, imprime el resultado:

    • add: Imprime Added [quantity] [product]
    • get: Imprime [product]: [stock] in stock
    • remove: Imprime Removed [quantity] [product] si tiene éxito, o Insufficient 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, o remove)
    • Nombre del producto
    • Cantidad (solo para operaciones add y remove)

Por ejemplo, dado:

5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples

Tu salida debería ser:

Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stock

El 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
			
		}
	}
}
quiz iconPonte a prueba

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