Semáforos
Semáforos: teoría y API
El semáforo, propuesto por Dijkstra en 1965, sigue siendo la herramienta mental más útil para coordinar una planta: un contador entero sobre el que solo se puede wait (restar, bloqueándose si vale 0) y signal (sumar, despertando a quien esperaba). Todo lo demás —colas, barreras, pipelines— se construye con ellos.
Las dos operaciones
| Operación | También llamada | Qué hace |
|---|---|---|
wait(s) | P, acquire, down | Si s > 0, lo decrementa y sigue. Si vale 0, bloquea al hilo hasta que otro haga signal. |
signal(s) | V, release, up | Si hay hilos esperando, despierta a uno. Si no, incrementa el valor. |
El valor del semáforo tiene lectura física: cuántos permisos quedan. Un semáforo vale 1 = «el recurso está libre» (mutex). Vale N = «hay N huecos / N máquinas libres». Vale 0 = «aún no ha ocurrido el evento» (señal).
Tipos de semáforo
- Binario (0 ó 1): como señal de evento o como mutex. En el cuaderno SCTR,
bsem. - Contador (0…N): recursos con varias unidades, buffers con capacidad.
El primer ejercicio de multitarea del cuaderno pide construir un semáforo contador a partir de semáforos binarios (uno de acceso y uno de bloqueo, con contador de bloqueados). Más abajo está su equivalente C++ moderno.
API en C++20: <semaphore>
#include <semaphore>
std::counting_semaphore<5> huecos(5); // semáforo contador, máx. 5, empieza en 5
std::binary_semaphore listo(0); // semáforo binario, empieza en 0
huecos.acquire(); // wait: bloquea si vale 0
huecos.release(); // signal
if (huecos.try_acquire()) // wait sin bloqueo: devuelve bool
{ /* había permiso */ }
// variantes temporizadas:
huecos.try_acquire_for(std::chrono::seconds(2));
huecos.try_acquire_until(reloj::now() + std::chrono::seconds(2));
| Clásico | C++20 |
|---|---|
init(s, N) | Constructor: counting_semaphore<Max> s(N) |
wait(s) | s.acquire() |
signal(s) | s.release() |
| — | s.try_acquire(), try_acquire_for(d), try_acquire_until(t) |
Implementación propia (pre-C++20 o empotrados)
Con mutex + variable de condición se obtiene el semáforo clásico. Es la versión moderna del ej58:
#include <mutex>
#include <condition_variable>
class Semaforo {
int valor;
std::mutex m;
std::condition_variable cv;
public:
explicit Semaforo(int v) : valor(v) {}
void wait() { // P
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&] { return valor > 0; });
--valor;
}
void signal() { // V
std::lock_guard<std::mutex> lk(m);
++valor;
cv.notify_one();
}
bool try_wait() { // P sin bloqueo
std::lock_guard<std::mutex> lk(m);
if (valor > 0) { --valor; return true; }
return false;
}
};
Los cuatro usos industriales
| Uso | Valor inicial | Ejemplo de planta |
|---|---|---|
| Exclusión mutua | 1 | sRobot: el robot de la célula solo lo usa un hilo cada vez. |
| Señalización (evento) | 0 | scEntradaPrensa: «hay pieza en la prensa». |
| Conteo de recursos | N | sOperario = 10: diez máquinas disponibles para el operario. |
| Orden | 0 | Un hilo no arranca hasta que otro hace signal. |
El convenio de nombres sp / sc
En el estilo de los ejercicios, cada relación productor–consumidor usa una pareja de semáforos:
sp*— semáforo del productor: cuenta los huecos disponibles (inicializado a la capacidad).sc*— semáforo del consumidor: cuenta los elementos disponibles (inicializado a 0).
Por ejemplo, entre la prensa y el CNC: spPrensaCNC = 1 (el CNC admite una pieza) y
scPrensaCNC = 0 (de momento no hay ninguna). El productor hace
wait(sp…) antes de dejar la pieza y signal(sc…) después; el consumidor hace justo lo
inverso. Lo verás funcionando en el laboratorio FMS y se desarrolla en
Patrones con semáforos.
Ante cualquier semáforo pregúntate: ¿qué permiso representa una unidad? Si la respuesta es «hueco», el productor lo espera; si es «elemento», lo espera el consumidor; si es «recurso», lo esperan todos los que lo usan. Con eso, los wait/signal se colocan casi solos.