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
- La región crítica debe ser lo más corta posible: no hagas E/S lentas ni esperas bloqueantes dentro.
- Declara el mutex junto a los datos que protege; así es obvio qué protege.
- Un mutex por cada grupo de datos relacionados; no un «mutex global» que serialice toda la planta.
- Si necesitas esperar una condición dentro de la región crítica, usa una variable de condición, no un bucle de consulta con el mutex tomado.
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).