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ónTambién llamadaQué hace
wait(s)P, acquire, downSi s > 0, lo decrementa y sigue. Si vale 0, bloquea al hilo hasta que otro haga signal.
signal(s)V, release, upSi 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

📘 El ejercicio ej58

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ásicoC++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

UsoValor inicialEjemplo de planta
Exclusión mutua1sRobot: el robot de la célula solo lo usa un hilo cada vez.
Señalización (evento)0scEntradaPrensa: «hay pieza en la prensa».
Conteo de recursosNsOperario = 10: diez máquinas disponibles para el operario.
Orden0Un 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:

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.

💡 Cómo leer un semáforo

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.