Fundamentos

Mutex y candados RAII

Un mutex (exclusión mutua) es la versión moderna del semáforo binario usado como región crítica: solo un hilo puede tenerlo a la vez. En el estilo clásico de SCTR se escribe wait(s)/signal(s) con un semáforo inicializado a 1; en C++ se usa std::mutex junto a candados RAII que no se pueden olvidar abiertos.

std::mutex a mano (y por qué no)

std::mutex mtx;

mtx.lock();      // entra en la región crítica (bloquea si está ocupado)
// ... código protegido ...
mtx.unlock();    // sale de la región crítica

Este código es frágil: si el tramo protegido lanza una excepción o tiene un return intermedio, el unlock() no se ejecuta y el mutex queda cerrado para siempre (bloqueo de toda la instalación). Por eso en C++ casi nunca se usa lock()/unlock() a mano.

lock_guard: el candado automático

std::lock_guard toma el mutex al construirse y lo suelta al destruirse, pase lo que pase (principio RAII). Su ámbito marca la región crítica:

#include <mutex>

bool posicionesOcupadas[10] = {};      // almacén vertical compartido
uint32_t numerosSerie[10] = {};
std::mutex mtxAlmacen;                 // protege ambas matrices

void registra(int posicion, uint32_t serie) {
    std::lock_guard<std::mutex> lk(mtxAlmacen);  // entra
    posicionesOcupadas[posicion] = true;          // región crítica:
    numerosSerie[posicion] = serie;               // las dos escrituras juntas
}                                                 // sale automáticamente

Esto corresponde al ejercicio del almacén vertical: el elevador de entrada y el de salida consultan y modifican la misma tabla; sin el mutex, uno podría leer la tabla mientras el otro la actualiza a medias.

unique_lock: candado flexible

std::unique_lock es como lock_guard pero permite soltar y volver a tomar el mutex manualmente, y es obligatorio con variables de condición:

std::unique_lock<std::mutex> lk(mtx);
// ... trabajar protegido ...
lk.unlock();          // suelta antes de una operación larga
operacionLargaSinDatosCompartidos();
lk.lock();            // vuelve a proteger
// ... seguir ...

scoped_lock: varios mutex sin deadlock

Cuando una operación necesita dos recursos (por ejemplo, mover una pieza de la prensa al CNC requiere reservar ambos), std::scoped_lock toma los dos con un algoritmo que evita el interbloqueo:

std::mutex mtxPrensa, mtxCNC;

void transfiere() {
    std::scoped_lock lk(mtxPrensa, mtxCNC);  // toma ambos de forma segura
    // ... mover pieza de prensa a CNC ...
}

El patrón clásico: semáforo de región crítica

En el cuaderno de ejercicios estos mutex aparecen como semáforos binarios con nombres descriptivos: sPantalla (una única pantalla de operador), sDatos (datos de un regulador), sRobot (el robot de la célula). Su traducción directa:

// Estilo clásico                        // C++ moderno
// semaforo sPantalla = 1;              std::mutex mtxPantalla;
// semaforo sDatos = 1;                 std::mutex mtxDatos;

void visualiza(const std::string& mensaje) {
    // wait(sPantalla);
    std::lock_guard<std::mutex> lk(mtxPantalla);
    std::cout << mensaje << "\n";       // la pantalla es de uno en uno
    // signal(sPantalla);  ← implícito al salir del ámbito
}

Buenas prácticas

⚠ Cuidado

Un mutex no protege los datos por sí solo: protege a los hilos que lo usan. Si un hilo accede a la tabla del almacén sin tomar mtxAlmacen, hay carrera igualmente. La disciplina la pone el programador (o un tipo envoltorio, como la cola segura de Colas).