logo

Semaphore in der Prozesssynchronisation

Semaphoren sind ganz normale Variablen, die dazu dienen, die Aktivitäten mehrerer Prozesse in einem Computersystem zu koordinieren. Sie werden verwendet, um gegenseitigen Ausschluss zu erzwingen, Race Conditions zu vermeiden und die Synchronisierung zwischen Prozessen zu implementieren.

Der Prozess der Verwendung von Semaphoren sieht zwei Operationen vor: Warten (P) und Signal (V). Die Warteoperation verringert den Wert des Semaphors und die Signaloperation erhöht den Wert des Semaphors. Wenn der Wert des Semaphors Null ist, wird jeder Prozess, der eine Warteoperation ausführt, blockiert, bis ein anderer Prozess eine Signaloperation ausführt.



Semaphoren werden verwendet, um kritische Abschnitte zu implementieren, bei denen es sich um Codebereiche handelt, die jeweils nur von einem Prozess ausgeführt werden dürfen. Mithilfe von Semaphoren können Prozesse den Zugriff auf gemeinsam genutzte Ressourcen wie gemeinsam genutzten Speicher oder E/A-Geräte koordinieren.

Ein Semaphor ist eine besondere Art von Synchronisationsdaten, die nur über bestimmte Synchronisationsprimitive verwendet werden können. Wenn ein Prozess eine Warteoperation für ein Semaphor ausführt, prüft die Operation, ob der Wert des Semaphors> 0 ist. Wenn ja, verringert es den Wert des Semaphors und lässt den Prozess seine Ausführung fortsetzen; andernfalls blockiert es den Prozess auf dem Semaphor. Eine Signaloperation an einem Semaphor aktiviert einen Prozess, der ggf. am Semaphor blockiert ist, oder erhöht den Wert des Semaphors um 1. Aufgrund dieser Semantik werden Semaphore auch Zählsemaphore genannt. Der Anfangswert eines Semaphors bestimmt, wie viele Prozesse den Wartevorgang überwinden können.

Es gibt zwei Arten von Semaphoren:



  1. Binäres Semaphor –
    Dies wird auch als Mutex-Sperre bezeichnet. Es kann nur zwei Werte haben – 0 und 1. Sein Wert wird auf 1 initialisiert. Es wird verwendet, um die Lösung kritischer Abschnittsprobleme mit mehreren Prozessen zu implementieren.
  2. Zählsemaphor –
    Sein Wert kann sich über eine uneingeschränkte Domäne erstrecken. Es wird verwendet, um den Zugriff auf eine Ressource zu steuern, die über mehrere Instanzen verfügt.

Lassen Sie uns nun sehen, wie es funktioniert.

Schauen Sie sich zunächst zwei Operationen an, mit denen Sie auf den Wert der Semaphorvariablen zugreifen und diesen ändern können.

P- und V-Betrieb im Betriebssystem



Einige Punkte zum P- und V-Betrieb:

  1. Die P-Operation wird auch als Warte-, Schlaf- oder Down-Operation bezeichnet, und die V-Operation wird auch als Signal-, Wake-up- oder Up-Operation bezeichnet.
  2. Beide Operationen sind atomar und Semaphor(e) werden immer auf eins initialisiert. Hier bedeutet atomar, dass die Variable, an der gelesen, geändert und aktualisiert wird, zur gleichen Zeit/im gleichen Moment ohne Vorrang erfolgt, d. h. zwischen dem Lesen, Ändern und Aktualisieren wird kein anderer Vorgang ausgeführt, der die Variable ändern könnte.
  3. Ein kritischer Abschnitt wird von beiden Vorgängen umgeben, um die Prozesssynchronisierung zu implementieren. Siehe das Bild unten. Der kritische Abschnitt von Prozess P liegt zwischen P- und V-Betrieb.

Sehen wir uns nun an, wie es den gegenseitigen Ausschluss umsetzt. Angenommen, es gäbe zwei Prozesse P1 und P2 und ein Semaphor s wird mit 1 initialisiert. Wenn nun angenommen wird, dass P1 in seinen kritischen Abschnitt eintritt, wird der Wert von Semaphor s zu 0. Wenn nun P2 in seinen kritischen Abschnitt eintreten möchte, wartet es bis s> 0, dies kann nur passieren, wenn P1 seinen kritischen Abschnitt beendet und die V-Operation für Semaphore s aufruft.

Auf diese Weise wird ein gegenseitiger Ausschluss erreicht. Schauen Sie sich das Bild unten für Details an, bei dem es sich um ein binäres Semaphor handelt.


Implementierung: Binäre Semaphore

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(semaphore s) { if (s.value == 1) { s.value = 0;  } else { // Den Prozess zur Warteschlange hinzufügen q.push(P) sleep();  } } V(semaphore s) { if (s.q ist leer) { s.value = 1;  } else { // Einen Prozess aus der Warteschlange auswählen Process p = q.front();  // Den Prozess aus dem Wartezustand entfernen, da er für CS q.pop(); // gesendet wurde.  Aufwachen);  } } // Dieser Code wurde von Susobhan Akhuli>'> geändertC'> geändertJava 
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 = neue Warteschlange();  public void P(Semaphore s, Process p) { if (s.value == value.One) { s.value = value.Zero;  } else { // Den Prozess zur Warteschlange hinzufügen q.Enqueue(p);  p.Sleep();  } } public void V(Semaphore s) { if (s.q.Count == 0) { s.value = value.One;  } else { // Einen Prozess aus der Warteschlange auswählen Process p = q.Peek();  // den Prozess aus dem Wartezustand entfernen, // für CS q.Dequeue();  p.Wakeup();  } } }>
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);  }  } }>

Die obige Beschreibung bezieht sich auf ein binäres Semaphor, das nur die beiden Werte 0 und 1 annehmen und einen gegenseitigen Ausschluss gewährleisten kann. Es gibt einen anderen Semaphortyp, der Zählsemaphor genannt wird und Werte größer als eins annehmen kann.

Nehmen wir nun an, dass es eine Ressource gibt, deren Anzahl an Instanzen 4 beträgt. Jetzt initialisieren wir S = 4 und der Rest ist derselbe wie beim binären Semaphor. Immer wenn der Prozess diese Ressource benötigt, ruft er P auf oder wartet auf eine Funktion, und wenn er fertig ist, ruft er V oder eine Signalfunktion auf. Wenn der Wert von S Null wird, muss ein Prozess warten, bis S positiv wird. Angenommen, es gibt 4 Prozesse P1, P2, P3, P4, und alle rufen die Warteoperation auf S auf (initialisiert mit 4). Wenn ein anderer Prozess P5 die Ressource benötigt, sollte er warten, bis einer der vier Prozesse die Signalfunktion aufruft und der Wert von Semaphore positiv wird.

Einschränkungen :

  1. Eine der größten Einschränkungen von Semaphoren ist die Prioritätsumkehr.
  2. Deadlock: Angenommen, ein Prozess versucht, einen anderen Prozess aufzuwecken, der sich nicht im Ruhezustand befindet. Daher kann ein Deadlock auf unbestimmte Zeit blockieren.
  3. Das Betriebssystem muss alle Anrufe verfolgen, um zu warten und dem Semaphor ein Signal zu geben.

Problem bei dieser Implementierung eines Semaphors:

Das Hauptproblem bei Semaphoren besteht darin, dass sie geschäftiges Warten erfordern. Wenn sich ein Prozess im kritischen Abschnitt befindet, warten andere Prozesse, die versuchen, in den kritischen Abschnitt einzutreten, bis der kritische Abschnitt von keinem Prozess mehr belegt ist. Immer wenn ein Prozess wartet, prüft er kontinuierlich den Semaphorwert (siehe diese Zeile während (s==0); im P-Betrieb) und verschwendet CPU-Zyklus.

Es besteht auch die Möglichkeit eines Spinlocks, da die Prozesse weiterlaufen, während sie auf die Sperre warten. Um dies zu vermeiden, wird unten eine weitere Implementierung bereitgestellt.

Implementierung: Zählsemaphor

CPP
struct Semaphore {  int value;  // q contains all Process Control Blocks(PCBs)  // corresponding to processes got blocked  // while performing down operation.  QueueQ; }; P(Semaphor s) { s.value = s.value - 1;  if (s.value< 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;  öffentliches Semaphor (int value) { this.value = value;  q = new LinkedList();  } public void P(Process p) { value--;  wenn (Wert< 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 = neue Warteschlange();  public void P(Process p) { value--;  wenn (Wert< 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>

In dieser Implementierung wird der Prozess jedes Mal, wenn er wartet, zu einer Warteschlange von Prozessen hinzugefügt, die diesem Semaphor zugeordnet sind. Dies geschieht durch den Systemaufruf block() für diesen Prozess. Wenn ein Prozess abgeschlossen ist, ruft er die Signalfunktion auf und ein Prozess in der Warteschlange wird fortgesetzt. Es verwendet den Systemaufruf wakeup().

Vorteile von Semaphoren:

  • Ein einfacher und effektiver Mechanismus zur Prozesssynchronisierung
  • Unterstützt die Koordination zwischen mehreren Prozessen
  • Bietet eine flexible und robuste Möglichkeit, gemeinsam genutzte Ressourcen zu verwalten.
  • Es kann verwendet werden, um kritische Abschnitte in einem Programm zu implementieren.
  • Es kann verwendet werden, um Rennbedingungen zu vermeiden.

Nachteile von Semaphoren:

  • Aufgrund des mit Warte- und Signalvorgängen verbundenen Overheads kann es zu Leistungseinbußen kommen.
  • Kann bei falscher Verwendung zu einem Deadlock führen.
  • Es wurde 1965 von Dijkstra vorgeschlagen und ist eine sehr wichtige Technik zur Verwaltung gleichzeitiger Prozesse mithilfe eines einfachen ganzzahligen Werts, der als Semaphor bekannt ist. Ein Semaphor ist einfach eine ganzzahlige Variable, die von Threads gemeinsam genutzt wird. Diese Variable wird verwendet, um das Problem des kritischen Abschnitts zu lösen und eine Prozesssynchronisation in der Multiprozessorumgebung zu erreichen.
  • Bei unsachgemäßer Verwendung kann es zu Leistungsproblemen in einem Programm kommen.
  • Das Debuggen und Warten kann schwierig sein.
  • Bei unsachgemäßer Verwendung kann es zu Race Conditions und anderen Synchronisierungsproblemen kommen.
  • Es kann für bestimmte Arten von Angriffen anfällig sein, beispielsweise für Denial-of-Service-Angriffe.