I semafori sono solo normali variabili utilizzate per coordinare le attività di più processi in un sistema informatico. Vengono utilizzati per imporre la mutua esclusione, evitare condizioni di competizione e implementare la sincronizzazione tra i processi.
Il processo di utilizzo dei semafori prevede due operazioni: attesa (P) e segnale (V). L'operazione di attesa diminuisce il valore del semaforo e l'operazione di segnale incrementa il valore del semaforo. Quando il valore del semaforo è zero, qualsiasi processo che esegue un'operazione di attesa verrà bloccato finché un altro processo non esegue un'operazione di segnale.
I semafori vengono utilizzati per implementare sezioni critiche, ovvero regioni di codice che devono essere eseguite da un solo processo alla volta. Utilizzando i semafori, i processi possono coordinare l'accesso alle risorse condivise, come la memoria condivisa o i dispositivi I/O.
Un semaforo è un tipo speciale di dati di sincronizzazione che può essere utilizzato solo tramite specifiche primitive di sincronizzazione. Quando un processo esegue un'operazione di attesa su un semaforo, l'operazione controlla se il valore del semaforo è>0. In tal caso decrementa il valore del semaforo e lascia che il processo continui la sua esecuzione; in caso contrario blocca il processo sul semaforo. Un'operazione di segnale su un semaforo attiva un processo bloccato sul semaforo, se presente, o incrementa il valore del semaforo di 1. A causa di questa semantica, i semafori sono anche chiamati semafori di conteggio. Il valore iniziale di un semaforo determina quanti processi possono superare l'operazione di attesa.
riduzione di Python
I semafori sono di due tipi:
- Semaforo binario –
Questo è anche noto come blocco mutex. Può avere solo due valori: 0 e 1. Il suo valore è inizializzato su 1. Viene utilizzato per implementare la soluzione di problemi di sezione critica con più processi. - Conteggio del semaforo –
Il suo valore può variare su un dominio illimitato. Viene utilizzato per controllare l'accesso a una risorsa che ha più istanze.
Ora vediamo come lo fa.
Innanzitutto, esamina due operazioni che possono essere utilizzate per accedere e modificare il valore della variabile semaforo.

Alcuni punti riguardanti il funzionamento P e V:
Java bool in stringa
- L'operazione P è anche chiamata operazione di attesa, sospensione o inattività, mentre l'operazione V è anche chiamata operazione di segnale, riattivazione o operazione di su.
- Entrambe le operazioni sono atomiche e il semaforo è sempre inizializzato su uno. Qui atomico significa quella variabile su cui lettura, modifica e aggiornamento avvengono nello stesso momento/momento senza prelazione, ovvero tra lettura, modifica e aggiornamento non viene eseguita nessun'altra operazione che possa modificare la variabile.
- Una sezione critica è circondata da entrambe le operazioni per implementare la sincronizzazione del processo. Vedi l'immagine qui sotto. La sezione critica del processo P si trova tra le operazioni P e V.

Ora vediamo come implementa la mutua esclusione. Lasciamo che ci siano due processi P1 e P2 e un semaforo s sia inizializzato come 1. Ora se supponiamo che P1 entri nella sua sezione critica, allora il valore del semaforo s diventa 0. Ora se P2 vuole entrare nella sua sezione critica, aspetterà fino a s> 0, questo può accadere solo quando P1 termina la sua sezione critica e chiama l'operazione V sul semaforo s.
In questo modo si ottiene la mutua esclusione. Guarda l'immagine qui sotto per i dettagli che è un semaforo binario.

Implementazione: Semafori binari
struct semaphore { enum value(0, 1); // q contains all Process Control Blocks (PCBs) // corresponding to processes got blocked // while performing down operation. QueueQ; }; P(semaforo s) { if (s.valore == 1) { s.valore = 0; } else { // aggiunge il processo alla coda di attesa q.push(P) sleep(); } } V(semaforo s) { if (s.q è vuoto) { s.value = 1; } else { // seleziona un processo dalla coda di attesa Processo p = q.front(); // rimuove il processo dall'attesa poiché è stato // inviato per CS q.pop(); sveglia(p); } } // Questo codice è stato modificato da Susobhan Akhuli> C #include #include #include struct semaphore{ QueueQ; valore intero; }; void P(struct semaforo s) { if (s.value == 1) { s.value = 0; } else { s.q.push(P); sonno(); } } void V(semaforo s) { if (s.q è vuoto) { s.value = 1; } else { // Ottieni un processo dal processo della coda di attesa p = q.front(); // Rimuove il processo dall'attesa q.pop(); sveglia(p); } } int main() { printf('Questo è Hemish!!'); // Questo codice è stato fornito dal contributo di Himesh Singh Chauhan return 0; } // Questo codice è stato modificato da Susobhan Akhuli> Giava import java.util.*; class Semaphore { public enum Value { Zero, One } public Queueq = nuova lista collegata(); valore pubblico valore = Valore.Uno; public void P(Semaforo s, Processo p) { if (s.valore == Valore.Uno) { s.valore = Valore.Zero; } else { // aggiunge il processo alla coda di attesa q.add(p); p.Sonno(); } } public void V(Semaforo s) { if (s.q.size() == 0) { s.value = Value.One; } else { // seleziona un processo dalla coda di attesa Processo p = q.peek(); // rimuove il processo dall'attesa poiché è stato // inviato per CS q.remove(); p.Sveglia(); } } }> 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 = nuova coda(); public void P(Semaforo s, Processo p) { if (s.valore == valore.Uno) { s.valore = valore.Zero; } else { // aggiunge il processo alla coda di attesa q.Enqueue(p); p.Sonno(); } } public void V(Semaforo s) { if (s.q.Count == 0) { s.value = value.One; } else { // seleziona un processo dalla coda di attesa Processo p = q.Peek(); // rimuove il processo dall'attesa poiché è stato // inviato per CS q.Dequeue(); p.Sveglia(); } } }> 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 descrizione sopra riguarda il semaforo binario che può assumere solo due valori 0 e 1 e garantire la mutua esclusione. Esiste un altro tipo di semaforo chiamato semaforo di conteggio che può assumere valori maggiori di uno.
java converte la stringa in int
Supponiamo ora che esista una risorsa il cui numero di istanze è 4. Ora inizializziamo S = 4 e il resto è lo stesso del semaforo binario. Ogni volta che il processo desidera quella risorsa, chiama P o attende la funzione e quando ha finito chiama V o segnala la funzione. Se il valore di S diventa zero, il processo deve attendere finché S diventa positivo. Ad esempio, supponiamo che ci siano 4 processi P1, P2, P3, P4 e che tutti chiamino l'operazione di attesa su S (inizializzato con 4). Se un altro processo P5 desidera la risorsa, allora dovrebbe attendere finché uno dei quattro processi non chiama la funzione segnale e il valore del semaforo diventa positivo.
Limitazioni:
- Uno dei maggiori limiti del semaforo è l'inversione della priorità.
- Deadlock, supponiamo che un processo stia tentando di riattivare un altro processo che non è in uno stato di sospensione. Pertanto, una situazione di stallo può bloccarsi indefinitamente.
- Il sistema operativo deve tenere traccia di tutte le chiamate in attesa e segnalare al semaforo.
Problema in questa implementazione di un semaforo:
Il problema principale con i semafori è che richiedono un'attesa occupata. Se un processo si trova nella sezione critica, gli altri processi che tentano di entrare nella sezione critica aspetteranno finché la sezione critica non sarà occupata da alcun processo. Ogni volta che un processo attende, controlla continuamente il valore del semaforo (guarda questa riga while (s==0); nell'operazione P) e spreca il ciclo della CPU.
Esiste anche la possibilità di spinlock poiché i processi continuano a girare mentre aspettano il blocco. Per evitare ciò, di seguito viene fornita un'altra implementazione.
proprietà acide
Implementazione: Conteggio del semaforo
CPP struct Semaphore { int value; // q contains all Process Control Blocks(PCBs) // corresponding to processes got blocked // while performing down operation. QueueQ; }; P(Semaforo s) { s.valore = s.valore - 1; if (s.valore< 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; }> Giava import java.util.LinkedList; import java.util.Queue; // semaphore class class Semaphore { // our value int value; QueueQ; public Semaforo(int valore) { this.valore = valore; q = nuova ListaCollegata(); } public void P(Processo p) { valore--; se (valore< 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 = nuova coda(); public void P(Processo p) { valore--; se (valore< 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 questa implementazione ogni volta che il processo attende viene aggiunto ad una coda di attesa di processi associati a quel semaforo. Questo viene fatto tramite la chiamata di sistema block() su quel processo. Quando un processo è completato, chiama la funzione signal e un processo in coda viene ripreso. Utilizza la chiamata di sistema wakeup().
Vantaggi dei semafori:
- Un meccanismo semplice ed efficace per la sincronizzazione dei processi
- Supporta il coordinamento tra più processi
- Fornisce un modo flessibile e robusto per gestire le risorse condivise.
- Può essere utilizzato per implementare sezioni critiche in un programma.
- Può essere utilizzato per evitare condizioni di gara.
Svantaggi dei semafori:
- Può portare a un degrado delle prestazioni a causa del sovraccarico associato alle operazioni di attesa e segnale.
- Può provocare un deadlock se utilizzato in modo errato.
- È stata proposta da Dijkstra nel 1965 ed è una tecnica molto significativa per gestire processi concorrenti utilizzando un semplice valore intero, noto come semaforo. Un semaforo è semplicemente una variabile intera condivisa tra i thread. Questa variabile viene utilizzata per risolvere il problema della sezione critica e per ottenere la sincronizzazione dei processi nell'ambiente multiprocessing.
- Può causare problemi di prestazioni in un programma se non utilizzato correttamente.
- Può essere difficile eseguire il debug e la manutenzione.
- Può essere soggetto a condizioni di gara e altri problemi di sincronizzazione se non utilizzato correttamente.
- Può essere vulnerabile a determinati tipi di attacchi, come gli attacchi di negazione del servizio.