Semáforos
Patrones con semáforos
Con wait y signal se construyen unos pocos patrones que resuelven el 95 % de las sincronizaciones de una planta. Cada patrón se presenta con su esquema de semáforos y su valor inicial, que es lo que hay que aprender a decidir.
1 · Cita (rendezvous): «espérame aquí»
Dos hilos deben llegar a un punto antes de continuar. Ejemplo: el robot no puede empezar hasta que el hilo de seguridad haya terminado la comprobación de la barrera óptica.
Semaforo sListo(0); // empieza en 0: evento pendiente
void hSeguridad() {
comprobarBarreras(); // operación larga
sListo.signal(); // «ya está»
}
void hRobot() {
sListo.wait(); // espera la comprobación
trabajar();
}
Para cita completa entre N hilos (todos esperan a todos) se usa una barrera.
2 · Orden entre etapas: «primero A, luego B»
Generalización del anterior para imponer secuencias. Cada flecha del diagrama de la instalación es un semáforo inicializado a 0:
Semaforo sLleno(0), sCalentado(0);
void hLlenado() { llenarDeposito(); sLleno.signal(); }
void hCalentador() { sLleno.wait(); calentar(); sCalentado.signal(); }
void hMezclador() { sCalentado.wait(); mezclar(); }
3 · Pool de recursos: «hay N disponibles»
Un semáforo contador con N permisos limita cuántos hilos usan un recurso compartido. Del cuaderno: un operario atendiendo 10 máquinas, o 7 robots máximo alrededor de una cuba.
Semaforo sMaquinas(10); // 10 máquinas libres al arrancar
void hTrabajo() {
sMaquinas.wait(); // reserva una máquina
usarMaquina();
sMaquinas.signal(); // la devuelve al terminar
}
El valor del semáforo en cada instante dice cuántas máquinas quedan libres: diagnóstico gratis.
4 · Exclusión mutua: «de uno en uno»
Semáforo binario inicializado a 1 como región crítica. En los ejercicios: sRobot (el robot de
la célula), sPantalla (la única pantalla), sMensaje (el mensaje del panel). En C++
moderno se prefiere std::mutex + lock_guard, pero el razonamiento es idéntico:
Semaforo sRobot(1);
void muevePieza(int desde, int hasta) {
sRobot.wait(); // pedimos el robot
coge(desde);
deja(hasta);
sRobot.signal(); // lo devolvemos SIEMPRE
}
Si una ruta sale de la región crítica sin signal (un return anticipado, una excepción), el
recurso queda secuestrado y la planta se para. En C++ usa lock_guard/unique_lock;
si usas semáforos, estructura el código para que el signal esté siempre en el camino de salida.
5 · Pareja productor/consumidor (sp/sc)
El patrón central de toda línea de producción. Entre dos estaciones hay una posición (o buffer) de capacidad C, y se gobierna con dos semáforos:
Semaforo sp(C); // huecos libres: empieza en C
Semaforo sc(0); // elementos listos: empieza en 0
void productor() {
sp.wait(); // ¿hay hueco?
depositar(elemento);
sc.signal(); // hay un elemento más
}
void consumidor() {
sc.wait(); // ¿hay elemento?
retirar(elemento);
sp.signal(); // hay hueco de nuevo
}
Si la posición solo cabe una pieza (una prensa, una plataforma, una celda de entrada), C = 1. Si es un pulmón o almacén intermedio, C es su capacidad. Además, si el depósito es una estructura de datos compartida, se añade un tercer semáforo mutex para protegerla — el patrón completo está en Productor–consumidor y se puede tocar en el laboratorio de envasado.
6 · Limitación de ritmo: «no más de N por minuto»
Un hilo repone permisos a ritmo fijo y los consumidores los gastan: sirve para limitar arranques de motores, accesos a un horno, inyección de piezas…
Semaforo sTurnos(0);
void hReloj() { // repone un permiso cada 2 s
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(2));
sTurnos.signal();
}
}
void hInyector() {
sTurnos.wait(); // solo avanza si hay permiso
inyectarPieza();
}
Cómo elegir el semáforo adecuado
| Pregunta | Respuesta → semáforo |
|---|---|
| ¿Es un evento que ocurre una vez? | Binario a 0, el productor hace signal. |
| ¿Es un recurso de una sola unidad? | Binario a 1 (mutex). |
| ¿Hay N unidades del recurso? | Contador a N. |
| ¿Es un buffer entre estaciones? | Pareja sp/sc con capacidad C. |
| ¿N hilos deben esperarse entre sí? | Barrera (semáforos + contador). |