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.

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
			
		}
	}
}
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