logo

Semáforos en sincronización de procesos

Los semáforos son simplemente variables normales que se utilizan para coordinar las actividades de múltiples procesos en un sistema informático. Se utilizan para imponer la exclusión mutua, evitar condiciones de carrera e implementar la sincronización entre procesos.

El proceso de uso de semáforos proporciona dos operaciones: esperar (P) y señalar (V). La operación de espera disminuye el valor del semáforo y la operación de señal incrementa el valor del semáforo. Cuando el valor del semáforo es cero, cualquier proceso que realice una operación de espera será bloqueado hasta que otro proceso realice una operación de señal.



Los semáforos se utilizan para implementar secciones críticas, que son regiones de código que deben ser ejecutadas por un solo proceso a la vez. Al utilizar semáforos, los procesos pueden coordinar el acceso a recursos compartidos, como memoria compartida o dispositivos de E/S.

Un semáforo es un tipo especial de datos de sincronización que sólo se puede utilizar mediante primitivas de sincronización específicas. Cuando un proceso realiza una operación de espera en un semáforo, la operación verifica si el valor del semáforo es> 0. Si es así, disminuye el valor del semáforo y deja que el proceso continúe su ejecución; de lo contrario, bloquea el proceso en el semáforo. Una operación de señal en un semáforo activa un proceso bloqueado en el semáforo, si lo hay, o incrementa el valor del semáforo en 1. Debido a esta semántica, los semáforos también se denominan semáforos de conteo. El valor inicial de un semáforo determina cuántos procesos pueden pasar la operación de espera.

Los semáforos son de dos tipos:



  1. Semáforo binario –
    Esto también se conoce como bloqueo mutex. Sólo puede tener dos valores: 0 y 1. Su valor se inicializa en 1. Se utiliza para implementar la solución de problemas de secciones críticas con múltiples procesos.
  2. Contando semáforo –
    Su valor puede abarcar un dominio sin restricciones. Se utiliza para controlar el acceso a un recurso que tiene múltiples instancias.

Ahora veamos cómo lo hace.

multihilo java

Primero, observe dos operaciones que se pueden usar para acceder y cambiar el valor de la variable del semáforo.

Operación-P-y-V-en-OS



Algunos puntos sobre el funcionamiento de P y V:

  1. La operación P también se denomina operación de espera, suspensión o inactividad, y la operación V también se denomina operación de señal, activación o activación.
  2. Ambas operaciones son atómicas y los semáforos siempre se inicializan en uno. Aquí atómico significa esa variable en la que la lectura, modificación y actualización ocurren al mismo tiempo/momento sin preferencia, es decir, entre lectura, modificación y actualización no se realiza ninguna otra operación que pueda cambiar la variable.
  3. Una sección crítica está rodeada por ambas operaciones para implementar la sincronización de procesos. Vea la imagen a continuación. La sección crítica del Proceso P se encuentra entre las operaciones P y V.

Ahora veamos cómo implementa la exclusión mutua. Sean dos procesos P1 y P2 y un semáforo s se inicializa como 1. Ahora, si supongamos que P1 ingresa a su sección crítica, entonces el valor del semáforo s se vuelve 0. Ahora, si P2 quiere ingresar a su sección crítica, esperará hasta s.> 0, esto solo puede suceder cuando P1 termina su sección crítica y llama a la operación V en el semáforo s.

De esta manera se logra la exclusión mutua. Mire la imagen a continuación para obtener detalles sobre cuál es un semáforo binario.


Implementación: Semáforos binarios

c# contiene cadena
C++
struct semaphore {    enum value(0, 1);  // q contains all Process Control Blocks (PCBs)  // corresponding to processes got blocked  // while performing down operation.  Queueq; }; P(semáforo s) { if (s.valor == 1) { s.valor = 0;  } else { // agrega el proceso a la cola de espera q.push(P) sleep();  } } V(semaphore s) { if (s.q está vacío) { s.value = 1;  } else { // selecciona un proceso de la cola de espera Proceso p = q.front();  // elimina el proceso de espera tal como ha sido // enviado para CS q.pop();  levantarse);  } } // Este código es modificado por Susobhan Akhuli>
C
#include  #include #include struct semaphore{  Queueq;  valor entero; }; void P(struct semaphore s) { if (s.value == 1) { s.value = 0;  } else { sqpush(P);   dormir();  } } void V(semaphore s) { if (s.q está vacío) { s.value = 1;  } else { // Obtener un proceso del proceso de la cola de espera p = q.front();  // Quitar el proceso de la espera q.pop();  levantarse);  } } int main() { printf('¡¡Esto es Hemish!!');    // Este código es una contribución de Himesh Singh Chauhan return 0; } // Este código es modificado por Susobhan Akhuli>
Java
import java.util.*; class Semaphore {  public enum Value { Zero, One }  public Queueq = nueva lista vinculada();  Valor público valor = Valor.Uno;  public void P(Semáforo s, Proceso p) { if (s.value == Value.One) { s.value = Value.Zero;  } else { // agrega el proceso a la cola de espera q.add(p);  p.Dormir();  } } public void V(Semaphore s) { if (s.q.size() == 0) { s.value = Value.One;  } else { // selecciona un proceso de la cola de espera Proceso p = q.peek();  // elimina la espera del proceso tal como // ha sido enviado para CS q.remove();  p.Despertar();  } } }>
Python3
from enum import Enum from queue import Queue class Semaphore: class Value(Enum): Zero = 0 One = 1 def __init__(self): self.q = Queue() self.value = Semaphore.Value.One def P(self, s, p): if s.value == Semaphore.Value.One: s.value = Semaphore.Value.Zero else: # add the process to the waiting queue s.q.put(p) p.Sleep() def V(self, s): if s.q.qsize() == 0: s.value = Semaphore.Value.One else: # select a process from waiting queue p = s.q.queue[0] # remove the process from waiting as it has # been sent for CS s.q.get() p.Wakeup()>
C#
using System.Collections.Generic; class Semaphore {  public enum value { Zero, One }  public Queueq = nueva cola();  public void P(Semáforo s, Proceso p) { if (s.valor == valor.Uno) { s.valor = valor.Cero;  } else { // agrega el proceso a la cola de espera q.Enqueue(p);  p.Dormir();  } } public void V(Semaphore s) { if (sq.Count == 0) { s.value = value.One;  } else { // selecciona un proceso de la cola de espera Proceso p = q.Peek();  // eliminar el proceso de la espera tal como // se envió para CS q.Dequeue();  p.Despertar();  } } }>
JavaScript
class Semaphore {  constructor() {  this.value = 0;  // q contains all Process Control Blocks (PCBs)  // corresponding to processes got blocked  // while performing down operation.  this.q = [];  }  P() {  if (this.value == 1) {  this.value = 0;  } else {  // add the process to the waiting queue  this.q.push(P);  sleep();  }  }  V() {  if (this.q.length == 0) {  this.value = 1;  } else {  // select a process from waiting queue  let p = this.q.shift();  // remove the process from waiting as it has been  // sent for CS  wakeup(p);  }  } }>

La descripción anterior es para un semáforo binario que puede tomar sólo dos valores 0 y 1 y garantizar la exclusión mutua. Existe otro tipo de semáforo llamado semáforo de conteo que puede tomar valores mayores que uno.

Ahora supongamos que hay un recurso cuyo número de instancias es 4. Ahora inicializamos S = 4 y el resto es igual que para el semáforo binario. Siempre que el proceso quiere ese recurso, llama a P o espera la función y cuando termina, llama a V o función de señal. Si el valor de S se vuelve cero, entonces el proceso tiene que esperar hasta que S se vuelva positivo. Por ejemplo, supongamos que hay 4 procesos P1, P2, P3, P4 y todos llaman a la operación de espera en S (inicializado con 4). Si otro proceso P5 quiere el recurso, entonces debe esperar hasta que uno de los cuatro procesos llame a la función de señal y el valor del semáforo se vuelva positivo.

Limitaciones:

  1. Una de las mayores limitaciones del semáforo es la inversión de prioridad.
  2. Punto muerto, supongamos que un proceso está intentando despertar a otro proceso que no está en estado de suspensión. Por lo tanto, un punto muerto puede bloquearse indefinidamente.
  3. El sistema operativo debe realizar un seguimiento de todas las llamadas para esperar y señalar el semáforo.

Problema en esta implementación de un semáforo:

El principal problema con los semáforos es que requieren una espera ocupada. Si un proceso está en la sección crítica, otros procesos que intenten ingresar a la sección crítica estarán esperando hasta que la sección crítica no esté ocupada por ningún proceso. Siempre que un proceso espera, verifica continuamente el valor del semáforo (mire esta línea while (s==0); en operación P) y desperdicia el ciclo de la CPU.

También existe la posibilidad de que se produzca un bloqueo de giro, ya que los procesos continúan girando mientras esperan el bloqueo. Para evitar esto, a continuación se proporciona otra implementación.

Implementación: Contando semáforo

CPP
struct Semaphore {  int value;  // q contains all Process Control Blocks(PCBs)  // corresponding to processes got blocked  // while performing down operation.  Queueq; }; P(Semáforo s) { s.valor = s.valor - 1;  si (s.valor< 0) {  // add process to queue  // here p is a process which is currently executing  q.push(p);  block();  }  else  return; } V(Semaphore s) {  s.value = s.value + 1;  if (s.value <= 0) {  // remove process p from queue  Process p = q.pop();  wakeup(p);  }  else  return; }>
Java
import java.util.LinkedList; import java.util.Queue; // semaphore class  class Semaphore {  // our value  int value;  Queueq;  Semáforo público (valor int) { this.value = valor;  q = nueva ListaEnlazada();  } public void P(Proceso p) { valor--;  si (valor< 0) {  q.add(p);  p.block();  }  }  public void V()  {  value++;  if (value <= 0) {  Process p = q.remove();  p.wakeup();  }  } }>
Python3
import heapq # Global Variable to track the Processes going into Critical Section COUNTER=1 class Semaphore: def __init__(self,value): # Value of the Semaphore passed to the Constructor self.value=value # The Waiting queue which will be using the heapq module of Python self.q=list() def getSemaphore(self):  ''' Function to print the Value of the Semaphore Variable ''' print(f'Semaphore Value: {self.value}') def block(process): print(f'Process {process} Blocked.') def wakeup(process): print(f'Process {process} Waked Up and Completed it's work.') def P(s): global COUNTER s.value=s.value-1 if(s.value<0): heapq.heappush(s.q,COUNTER) block(COUNTER) else: print(f'Process {COUNTER} gone inside the Critical Section.') COUNTER+=1 return def V(s): global COUNTER s.value=s.value+1 if(s.value<=0): p=heapq.heappop(s.q) wakeup(p) COUNTER-=1 else: print(f'Process {COUNTER} completed it's work.') COUNTER-=1 return # Can Pass the Value of the Counting Semaphore to the Class Constructor # Example for Counting Semaphore value as 2 s1=Semaphore(2) s1.getSemaphore() P(s1) s1.getSemaphore() P(s1) s1.getSemaphore() P(s1) s1.getSemaphore() V(s1) s1.getSemaphore() V(s1) s1.getSemaphore() V(s1) s1.getSemaphore() # This Code is Contributed by Himesh Singh Chauhan>
C#
using System.Collections.Generic; public class Semaphore {  public int value;  // q contains all Process Control Blocks(PCBs)  // corresponding to processes got blocked  // while performing down operation.  Queueq = nueva cola();  public void P(Proceso p) { valor--;  si (valor< 0) {  // add process to queue  q.Enqueue(p);  p.block();  }  }  public void V()  {  value++;  if (value <= 0) {  // remove process p from queue  Process p = q.Dequeue();  p.wakeup();  }  } }>
javascript
// Define a Semaphore object function Semaphore() {  this.value = 0;  this.q = []; // Initialize an array to act as a queue } // Implement the P operation Semaphore.prototype.P = function(p) {  this.value = this.value - 1;  if (this.value < 0) {  // Add process to queue  this.q.push(p);  // Assuming block() and wakeup() functions are defined elsewhere  block();  } }; // Implement the V operation Semaphore.prototype.V = function() {  this.value = this.value + 1;  if (this.value <= 0) {  // Remove process p from queue  var p = this.q.shift();  // Assuming wakeup() function is defined elsewhere  wakeup(p);  } }; // This code is contributed by Susobhan Akhuli>

En esta implementación, cada vez que el proceso espera, se agrega a una cola de espera de procesos asociados con ese semáforo. Esto se hace a través del bloque de llamadas al sistema() en ese proceso. Cuando se completa un proceso, llama a la función de señal y se reanuda un proceso en la cola. Utiliza la llamada al sistema wakeup().

Ventajas de los semáforos:

  • Un mecanismo simple y eficaz para la sincronización de procesos.
  • Apoya la coordinación entre múltiples procesos.
  • Proporciona una forma flexible y sólida de gestionar recursos compartidos.
  • Se puede utilizar para implementar secciones críticas en un programa.
  • Se puede utilizar para evitar condiciones de carrera.

Desventajas de los semáforos:

  • Puede provocar una degradación del rendimiento debido a la sobrecarga asociada con las operaciones de espera y señal.
  • Puede provocar un punto muerto si se usa incorrectamente.
  • Fue propuesto por Dijkstra en 1965, que es una técnica muy importante para gestionar procesos concurrentes mediante el uso de un valor entero simple, que se conoce como semáforo. Un semáforo es simplemente una variable entera que se comparte entre subprocesos. Esta variable se utiliza para resolver el problema de la sección crítica y lograr la sincronización de procesos en el entorno de multiprocesamiento.
  • Puede causar problemas de rendimiento en un programa si no se usa correctamente.
  • Puede resultar difícil depurar y mantener.
  • Puede ser propenso a condiciones de carrera y otros problemas de sincronización si no se usa correctamente.
  • Puede ser vulnerable a ciertos tipos de ataques, como ataques de denegación de servicio.