Definición Conceptual

Los semáforos son mecanismo de sincronización entre dos o más procesos. Está compuesto por dos campos:

  • Un entero no negativo llamado . Se inicializa con un valor .
  • Un conjunto de procesos llamado . Se inicializa con el conjunto vacío .

El valor del semáforo representa la cantidad de recursos disponibles. Puede ser pensado como un contador.

  • Si el contador es mayor a cero, entonces el recurso está disponible.
  • Si el contador es cero, el recurso no está disponible.

Operaciones Definidas

Se definen dos operaciones atómicas sobre un semáforo :

  • La operación o wait(S) solicita un recurso. Si el contador es mayor a cero, se disminuye. Si el contador es cero, entonces se bloquea hasta que otro proceso lo libere con signal(S).

    if S.V > 0
    	S.V := S.V - 1
    else
    	// siendo p el propio proceso
    	S.L add p
    	p.state := blocked
  • La operación o signal(S) libera un recurso. Si no hay procesos esperando, decrementa el contador. Si no, directamente despierta un proceso en espera.

    if S.L is empty
    	S.V := S.V + 1
    else
    	// sea q un proceso arbitrario en espera
    	S.L remove q
    	p.state := ready

Invariantes de Semáforos

Los invariantes de los semáforos son:

  • El contador es siempre no negativo: .
  • El cambio del valor del contador únicamente depende de la cantidad de llamadas a sus operaciones básicas: .

Semáforos de UNIX

Existen dos tipos de semáforos:

  • System V
  • POSIX

Un semáforo System V está compuesto por:

  • El valor del semáforo.
  • El process id del último proceso que utilizó el semáforo.
  • La cantidad de procesos esperando por el semáforo.
  • La cantidad de procesos que está esperando que el semáforo sea cero. Esto permite que podamos implementar barreras.

Una barrera permite a múltiples hilos esperar hasta que todos los hilos hayan llegado a cierto punto.

Semáforos en Rust

Utilizaremos el crate std-semaphore.

  • Para crear un semáforo, utilizamos: fn new(k: usize).
  • Para obtener acceso, utilizamos fn acquire(&self).
  • Para liberar un semáforo, utilizamos: fn release(&self).
  • Para acceder con patrón RAII, utilizamos: fn access(&self). Esto permite que el semáforo se libere en cuanto la variable devuelta (una guarda) se vaya del entorno.