Un semáforo es una variable entera propuesta por E. W. Dijkstra (1965) para contar el número de "despertares" pendientes. Resuelve el problema de los despertares perdidos de sleep/wakeup descrito en Sistemas Operativos/Concurrencia.
Dijkstra las llamó P (proberen, probar) y V (verhogen, incrementar); Tanenbaum usa down y up:
s > 0, lo decrementa y continúa. Si s == 0, el proceso se duerme sin completar el down.s. Si había procesos dormidos en el semáforo, el sistema elige uno y le permite completar su down.Ambas operaciones son indivisibles (atómicas): una vez iniciada, ningún otro proceso puede acceder al semáforo hasta que termine. Normalmente se implementan como Sistemas Operativos/Llamadas al sistema, deshabilitando brevemente las interrupciones o usando TSL/XCHG en multiprocesadores.
Además de exclusión mutua, los semáforos sirven para sincronización: garantizar que ciertos eventos ocurren en cierto orden.
const int N = 100;
typedef int semaphore;
semaphore mutex = 1; /* controla el acceso a la región crítica */
semaphore empty = N; /* cuenta ranuras vacías del búfer */
semaphore full = 0; /* cuenta ranuras llenas del búfer */
void producer(void) {
int item;
while (TRUE) {
item = produce_item();
down(&empty);
down(&mutex);
insert_item(item);
up(&mutex);
up(&full);
}
}
void consumer(void) {
int item;
while (TRUE) {
down(&full);
down(&mutex);
item = remove_item();
up(&mutex);
up(&empty);
consume_item(item);
}
}
mutex se usa para exclusión mutua.full y empty se usan para sincronización.Si el productor invierte down(&empty) y down(&mutex) y el búfer está lleno, se bloquea con el mutex tomado: el consumidor nunca puede entrar y ambos quedan bloqueados para siempre. Es un interbloqueo — ver Sistemas Operativos/Interbloqueos.
Esta fragilidad motivó primitivas de más alto nivel: Sistemas Operativos/Mutex y monitores.
sem_init, sem_wait (down), sem_post (up), sem_destroy.