Che cos'è un semaforo?


349

Un semaforo è un concetto di programmazione che viene spesso utilizzato per risolvere problemi multi-threading. La mia domanda alla comunità:

Cos'è un semaforo e come lo usi?


14
un flag booleano il cui valore si basa sul fatto che un contatore intero abbia raggiunto il limite superiore designato. Offuscamento al massimo!
Sam,

Risposte:


399

Pensa ai semafori come buttafuori in una discoteca. Ci sono un numero dedicato di persone che sono ammesse contemporaneamente nel club. Se il club è pieno, nessuno è autorizzato ad entrare, ma non appena una persona lascia un'altra persona può entrare.

È semplicemente un modo per limitare il numero di consumatori per una risorsa specifica. Ad esempio, per limitare il numero di chiamate simultanee a un database in un'applicazione.

Ecco un esempio molto pedagogico in C # :-)

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace TheNightclub
{
    public class Program
    {
        public static Semaphore Bouncer { get; set; }

        public static void Main(string[] args)
        {
            // Create the semaphore with 3 slots, where 3 are available.
            Bouncer = new Semaphore(3, 3);

            // Open the nightclub.
            OpenNightclub();
        }

        public static void OpenNightclub()
        {
            for (int i = 1; i <= 50; i++)
            {
                // Let each guest enter on an own thread.
                Thread thread = new Thread(new ParameterizedThreadStart(Guest));
                thread.Start(i);
            }
        }

        public static void Guest(object args)
        {
            // Wait to enter the nightclub (a semaphore to be released).
            Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
            Bouncer.WaitOne();          

            // Do some dancing.
            Console.WriteLine("Guest {0} is doing some dancing.", args);
            Thread.Sleep(500);

            // Let one guest out (release one semaphore).
            Console.WriteLine("Guest {0} is leaving the nightclub.", args);
            Bouncer.Release(1);
        }
    }
}

6
se è come buttafuori in un night club, dovrebbe far entrare gli ospiti in sequenza, ma quando l'ho provato è casuale. Per esempio. L'ospite 40 è arrivato prima dell'ospite 39. C'è qualcosa che possiamo fare per controllarlo?
TNA,

2
@TNA: Sì, questo ha a che fare con il modo in cui vengono avviati i nuovi thread in questo esempio e non proprio nell'ambito della risposta.
Patrik Svensson,

5
L'analogia dei buttafuori è davvero epica, ma è interessante notare
Igor Brejc

Quale valore offrono i semafori nei sistemi distribuiti?
csandreas1,

198

L'articolo Mutex e semafori demistificati da Michael Barr è una breve introduzione su ciò che rende diversi i mutex e i semafori, e quando dovrebbero e non dovrebbero essere usati. Ho estratto alcuni paragrafi chiave qui.

Il punto chiave è che i mutex dovrebbero essere usati per proteggere le risorse condivise, mentre i semafori dovrebbero essere usati per la segnalazione. Generalmente non si dovrebbero usare i semafori per proteggere le risorse condivise, né i mutex per la segnalazione. Esistono problemi, ad esempio, con l'analogia dei buttafuori in termini di utilizzo dei semafori per proteggere le risorse condivise: è possibile utilizzarli in questo modo, ma può causare difficoltà nella diagnosi dei bug.

Mentre i mutex e i semafori hanno alcune somiglianze nella loro implementazione, dovrebbero sempre essere usati in modo diverso.

La risposta più comune (ma comunque errata) alla domanda posta in alto è che i mutex e i semafori sono molto simili, con l'unica differenza significativa che i semafori possono contare più di uno. Quasi tutti gli ingegneri sembrano comprendere correttamente che un mutex è un flag binario utilizzato per proteggere una risorsa condivisa garantendo l'esclusione reciproca all'interno di sezioni critiche del codice. Ma quando viene chiesto di ampliare l'uso di un "semaforo di conteggio", la maggior parte degli ingegneri, che varia solo in base al loro grado di sicurezza, esprime un po 'dell'opinione del libro di testo secondo cui questi vengono utilizzati per proteggere diverse risorse equivalenti.

...

A questo punto viene creata un'interessante analogia usando l'idea delle chiavi del bagno come protezione delle risorse condivise: il bagno. Se un negozio ha un unico bagno, una sola chiave sarà sufficiente per proteggere quella risorsa e impedire a più persone di utilizzarla contemporaneamente.

Se ci sono più bagni, si potrebbe essere tentati di abbinarli allo stesso modo e creare più chiavi: è simile a un semaforo che viene utilizzato in modo errato. Una volta che hai una chiave in realtà non sai quale bagno è disponibile e se segui questo percorso probabilmente finirai per usare i mutex per fornire tali informazioni e assicurarti di non prendere un bagno che è già occupato .

Un semaforo è lo strumento sbagliato per proteggere molte delle stesse risorse essenzialmente uguali, ma questo è il modo in cui molte persone lo pensano e lo usano. L'analogia dei buttafuori è nettamente diversa: non ci sono molti dello stesso tipo di risorsa, ma esiste una risorsa che può accettare più utenti simultanei. Suppongo che un semaforo possa essere usato in tali situazioni, ma raramente ci sono situazioni del mondo reale in cui l'analogia effettivamente detiene - è più spesso che ci sono molti dello stesso tipo, ma ancora singole risorse, come i bagni, che non possono essere utilizzate per di qua.

...

L'uso corretto di un semaforo è per la segnalazione da un'attività all'altra. Un mutex è pensato per essere preso e rilasciato, sempre in quell'ordine, da ogni attività che utilizza la risorsa condivisa che protegge. Al contrario, le attività che utilizzano i semafori segnalano o attendono, non entrambi. Ad esempio, l'attività 1 può contenere codice per inviare (ad esempio, segnale o incremento) un particolare semaforo quando viene premuto il pulsante "accensione" e l'attività 2, che riattiva il display, pende su quello stesso semaforo. In questo scenario, un compito è il produttore del segnale di evento; l'altro il consumatore.

...

Qui viene sottolineato che i mutex interferiscono in modo negativo con i sistemi operativi in ​​tempo reale, causando un'inversione di priorità in cui un'attività meno importante può essere eseguita prima di un'attività più importante a causa della condivisione delle risorse. In breve, ciò accade quando un'attività con priorità inferiore utilizza un mutex per afferrare una risorsa, A, quindi tenta di afferrare B, ma viene messa in pausa perché B non è disponibile. Mentre è in attesa, arriva un'attività con priorità più alta e ha bisogno di A, ma è già legata e da un processo che non è nemmeno in esecuzione perché sta aspettando B. Esistono molti modi per risolverlo, ma il più delle volte viene risolto modificando il mutex e il task manager. In questi casi il mutex è molto più complesso di un semaforo binario,

...

La causa della diffusa confusione moderna tra mutex e semafori è storica, in quanto risale all'invenzione del Semaforo del 1974 ("S" maiuscola, in questo articolo) di Djikstra. Prima di quella data, nessuno dei meccanismi di sincronizzazione e segnalazione delle attività sicuri per gli interrupt noti agli scienziati informatici era scalabile in modo efficiente per l'uso da parte di più di due attività. Il semaforo rivoluzionario, sicuro e scalabile di Dijkstra è stato applicato sia nella protezione della sezione critica che nella segnalazione. E così iniziò la confusione.

Tuttavia, in seguito è diventato ovvio per gli sviluppatori del sistema operativo, dopo la comparsa del RTOS preventivo basato sulla priorità (ad esempio, VRTX, circa 1980), la pubblicazione di articoli accademici che stabiliscono l'RMA e i problemi causati dall'inversione di priorità e un documento sulla priorità protocolli di ereditarietà nel 1990, 3 è diventato evidente che i mutex devono essere più che semplici semafori con un contatore binario.

Mutex: condivisione delle risorse

Semaforo: segnalazione

Non usare l'uno per l'altro senza un'attenta considerazione degli effetti collaterali.


10
Guarda questo documento PDF sulla concorrenza di Stanford. Guarda le pagine 8. La spiegazione sopra avrà più senso quindi ... vedi.stanford.edu/materials/icsppcs107/…
Kris Subramanian

3
Il piccolo libro dei semafori è una lettura preziosa su questi temi.
G. Bach,

@KrisSubramanian Grazie per il link. Ma il documento discute sui semafori e nulla sui mutex. Tuttavia, vuoi dire che il buffer condiviso nell'esempio potrebbe essere protetto usando Mutex? invece di avere 2 semafori emptyBuffer e fullBuffer
talekeDskobeDa

1
@Pramod True. Il collegamento non aggiunge alcuna nota relativa a Mutex. Ho aggiunto il link in modo che il lato semaforo delle cose diventi chiaro per i lettori SO. :) È interessante notare che in questo caso il buffer viene utilizzato senza alcun blocco poiché ha accesso in sequenza e in un formato circolare. cioè Writer scriverà su 0 e segnalerà al lettore di leggere da 0. Se il lettore non legge da 0 e segnala lo scrittore, allora lo scrittore bloccherà. Quindi non è necessario utilizzare un mutex per bloccare la risorsa comune. Questo è diverso dall'analogia con il bagno fornita sopra.
Kris Subramanian,

@Kris Subramanian: bel documento, ma includi piccoli misguidenses: la terza pagina inizia affermando che "ogni thread che blocca il semaforo dovrebbe essere attento a sbloccarlo" - possono essere sbloccati da qualsiasi thread. Se lo fai nello stesso thread, lo stai semplicemente usando come "brocken mutex". "Brocken" perché può ancora essere sbloccato involontariamente da altri thread - si verificano errori - e rompere la tua logica. Comunque bello doc, pensò.
Rustam A.

70

Mutex: accesso di un membro esclusivo a una risorsa

Semaforo: accesso di n membri a una risorsa

Cioè, un mutex può essere usato per sincronizzare l'accesso a un contatore, file, database, ecc.

Un sempahore può fare la stessa cosa ma supporta un numero fisso di chiamanti simultanei. Ad esempio, posso racchiudere le mie chiamate al database in un semaforo (3) in modo che la mia app multithread raggiunga il database con al massimo 3 connessioni simultanee. Tutti i tentativi verranno bloccati fino a quando uno dei tre slot non si apre. Fanno cose come fare l'ingenuo throttling davvero, davvero facile.


20
Secondo Richard W. Stevens, un mutex è in realtà un semaforo binario, con solo due possibili valori: 0 e 1.
Qiang Xu,

19
@QiangXu in Sistemi operativi interni e principi di progettazione di William Stallings, un semaforo binario è diverso da un mutex in un modo molto importante, e cito: "Una differenza chiave tra un mutex e un semaforo binario è che il processo che blocca il mutex deve essere quello per sbloccarlo. Al contrario, è possibile per un processo bloccare un semaforo binario e per un altro sbloccarlo. " .
Electric Coffee,

3
A rischio di commentare un thread non aggiornato, questo non è corretto. Come sopra menzionato da @AdamDavis, Semaphore non dovrebbe (deve?) Essere usato per l'accesso di n membri a una risorsa, cosa che dovrebbe comunque essere fatta usando un Mutex. Prendi in considerazione l'analogia del bagno nel Coffeeshop con più persone in attesa di accedere o altrimenti più bagni con chiavi simili ai bagni. Piuttosto Semaphore dovrebbe essere usato per la segnalazione tra compiti.
cspider,

21

Considera, un taxi che può ospitare un totale di 3 ( posteriori ) +2 ( anteriori ) persone incluso il conducente. Quindi, a semaphoreconsente solo 5 persone all'interno di un'auto alla volta. E un mutexconsente solo 1 persona su un solo sedile dell'auto.

Pertanto, Mutexè consentire l'accesso esclusivo per una risorsa ( come un thread del sistema operativo ) mentre a Semaphoreè consentire l'accesso per n numero di risorse alla volta.


19

@Brama:

Un semaforo è un modo per bloccare una risorsa in modo che sia garantito che mentre un pezzo di codice viene eseguito, solo questo pezzo di codice ha accesso a quella risorsa. Ciò impedisce a due thread di accedere contemporaneamente a una risorsa, il che può causare problemi.

Questo non è limitato a un solo thread. Un semaforo può essere configurato per consentire a un numero fisso di thread di accedere a una risorsa.


7
Questo è un commento, non una risposta.
Kaspersky,

11
Sì, ma penso di averlo scritto prima che i commenti venissero aggiunti a Stack Overflow. O non l'ho fatto, non ricordo davvero. Questa volta ho risposto in un commento però. :-)
Mats Fredriksson,

16

Il semaforo può anche essere usato come ... semaforo. Ad esempio se si dispone di più processi che accodano i dati a una coda e solo un'attività che consuma dati dalla coda. Se non si desidera che l'attività di consumo esegua costantemente il polling della coda per i dati disponibili, è possibile utilizzare il semaforo.

Qui il semaforo non è usato come meccanismo di esclusione, ma come meccanismo di segnalazione. Il task di consumo è in attesa sul semaforo. Il task di produzione sta pubblicando sul semaforo.

In questo modo l'attività di consumo è in esecuzione quando e solo quando vi sono dati da cancellare


11

Esistono due concetti essenziali per la creazione di programmi simultanei: sincronizzazione ed esclusione reciproca. Vedremo come questi due tipi di blocchi (i semafori sono più in genere una sorta di meccanismo di blocco) ci aiutano a raggiungere la sincronizzazione e l'esclusione reciproca.

Un semaforo è un costrutto di programmazione che ci aiuta a raggiungere la concorrenza, implementando sia la sincronizzazione che l'esclusione reciproca. I semafori sono di due tipi, binario e conteggio.

Un semaforo ha due parti: un contatore e un elenco di attività in attesa di accedere a una particolare risorsa. Un semaforo esegue due operazioni: wait (P) [è come acquisire un lock] e release (V) [simile al rilascio di un lock] - queste sono le uniche due operazioni che si possono eseguire su un semaphore. In un semaforo binario, il contatore va logicamente tra 0 e 1. Puoi considerarlo simile a un lucchetto con due valori: aperto / chiuso. Un semaforo di conteggio ha più valori per count.

Ciò che è importante capire è che il contatore dei semafori tiene traccia del numero di attività che non devono essere bloccate, cioè che possono fare progressi. Le attività si bloccano e si aggiungono all'elenco del semaforo solo quando il contatore è zero. Pertanto, un'attività viene aggiunta all'elenco nella routine P () se non può progredire e "liberata" utilizzando la routine V ().

Ora è abbastanza ovvio vedere come i semafori binari possono essere usati per risolvere la sincronizzazione e l'esclusione reciproca: sono essenzialmente dei blocchi.

ex. Sincronizzazione:

thread A{
semaphore &s; //locks/semaphores are passed by reference! think about why this is so.
A(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
;// some block of code B2
...
}

//thread B{
semaphore &s;
B(semaphore &s): s(s){} //constructor
foo(){
...
...
// some block of code B1
s.V();
..
}

main(){
semaphore s(0); // we start the semaphore at 0 (closed)
A a(s);
B b(s);
}

Nell'esempio sopra, B2 può essere eseguito solo dopo che B1 ha terminato l'esecuzione. Diciamo che il thread A viene eseguito per primo - arriva a sem.P () e attende, poiché il contatore è 0 (chiuso). Il thread B arriva, finisce B1 e quindi libera il thread A - che quindi completa B2. Quindi otteniamo la sincronizzazione.

Vediamo ora l'esclusione reciproca con un semaforo binario:

thread mutual_ex{
semaphore &s;
mutual_ex(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
//critical section
s.V();
...
...
s.P();
//critical section
s.V();
...

}

main(){
semaphore s(1);
mutual_ex m1(s);
mutual_ex m2(s);
}

Anche l'esclusione reciproca è abbastanza semplice: m1 e m2 non possono entrare contemporaneamente nella sezione critica. Quindi ogni thread utilizza lo stesso semaforo per fornire l'esclusione reciproca per le sue due sezioni critiche. Ora, è possibile avere una maggiore concorrenza? Dipende dalle sezioni critiche. (Pensa a come altrimenti si potrebbero usare i semafori per ottenere l'esclusione reciproca ... suggerimento suggerimento: devo necessariamente usare solo un semaforo?)

Conteggio del semaforo: un semaforo con più di un valore. Diamo un'occhiata a ciò che questo implica: un lucchetto con più di un valore ?? Quindi aperto, chiuso e ... hmm. A che serve un blocco multi-stadio in mutua esclusione o sincronizzazione?

Prendiamo il più semplice dei due:

Sincronizzazione utilizzando un semaforo di conteggio: supponiamo che tu abbia 3 attività - # 1 e 2 che desideri eseguire dopo 3. Come progetteresti la sincronizzazione?

thread t1{
...
s.P();
//block of code B1

thread t2{
...
s.P();
//block of code B2

thread t3{
...
//block of code B3
s.V();
s.V();
}

Quindi se il tuo semaforo inizia chiuso, assicurati che i blocchi t1 e t2 vengano aggiunti all'elenco dei semafori. Poi arriva tutto l'importante t3, termina la sua attività e libera t1 e t2. In quale ordine vengono liberati? Dipende dall'attuazione dell'elenco dei semafori. Potrebbe essere FIFO, potrebbe essere basata una particolare priorità, ecc. (Nota: pensa a come sistemeresti le tue P e V se vuoi che t1 e t2 vengano eseguiti in un ordine particolare e se non eri a conoscenza dell'implementazione del semaforo)

(Scopri: cosa succede se il numero di V è maggiore del numero di P?)

Esclusione reciproca Uso dei semafori di conteggio: mi piacerebbe che tu costruissi il tuo pseudocodice per questo (ti fa capire meglio le cose!) - Ma il concetto fondamentale è questo: un semaforo di conteggio di counter = N consente a N attività di entrare liberamente nella sezione critica . Ciò significa che hai N task (o thread, se vuoi) entrare nella sezione critica, ma il N + 1 ° task viene bloccato (passa sul nostro elenco di attività bloccate preferito), e viene lasciato passare solo quando qualcuno V è il semaforo almeno una volta. Quindi il contatore dei semafori, invece di oscillare tra 0 e 1, ora passa tra 0 e N, consentendo a N compiti di entrare e uscire liberamente, bloccando nessuno!

Ora cavolo, perché avresti bisogno di una cosa così stupida? Il punto di reciproca esclusione non è forse quello di non consentire a più di un ragazzo di accedere a una risorsa ?? (Suggerimento Suggerimento ... Non sempre hai solo un'unità nel tuo computer, vero ...?)

A cui pensare : l'esclusione reciproca si ottiene avendo solo un semaforo di conteggio? Che cosa succede se si hanno 10 istanze di una risorsa e 10 thread entrano (attraverso il semaforo di conteggio) e si tenta di utilizzare la prima istanza?


7

Un semaforo è un oggetto contenente un numero naturale (cioè un numero intero maggiore o uguale a zero) sul quale sono definite due operazioni di modifica. Un'operazione V, aggiunge 1 al naturale. L'altra operazione, Pdiminuisce il numero naturale di 1. Entrambe le attività sono atomiche (ovvero nessuna altra operazione può essere eseguita contemporaneamente a Vo a P).

Poiché il numero naturale 0 non può essere ridotto, la chiamata Pa un semaforo contenente uno 0 bloccherà l'esecuzione del processo di chiamata (/ thread) fino a un momento in cui il numero non è più 0 e Ppuò essere eseguito (e atomicamente) con successo.

Come menzionato in altre risposte, i semafori possono essere utilizzati per limitare l'accesso a una determinata risorsa a un numero massimo (ma variabile) di processi.


7

Ho creato la visualizzazione che dovrebbe aiutare a comprendere l'idea. Semaphore controlla l'accesso a una risorsa comune in un ambiente multithreading. inserisci qui la descrizione dell'immagine

ExecutorService executor = Executors.newFixedThreadPool(7);

Semaphore semaphore = new Semaphore(4);

Runnable longRunningTask = () -> {
    boolean permit = false;
    try {
        permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
        if (permit) {
            System.out.println("Semaphore acquired");
            Thread.sleep(5);
        } else {
            System.out.println("Could not acquire semaphore");
        }
    } catch (InterruptedException e) {
        throw new IllegalStateException(e);
    } finally {
        if (permit) {
            semaphore.release();
        }
    }
};

// execute tasks
for (int j = 0; j < 10; j++) {
    executor.submit(longRunningTask);
}
executor.shutdown();

Produzione

Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore

Codice di esempio dall'articolo


3

Un flag hardware o software. Nei sistemi multi-tasking, un semaforo è una variabile con un valore che indica lo stato di una risorsa comune. Un processo che richiede la risorsa controlla il semaforo per determinare lo stato delle risorse e quindi decide come procedere.


2

I semafori funzionano come limitatori di thread.

Esempio: se si dispone di un pool di 100 thread e si desidera eseguire alcune operazioni DB. Se 100 thread accedono al DB in un determinato momento, potrebbe esserci un problema di blocco nel DB in modo da poter utilizzare il semaforo che consente solo un thread limitato alla volta. Esempio di esempio consente solo un thread alla volta. Quando un thread chiama il acquire()metodo, otterrà quindi l'accesso e dopo aver chiamato il release()metodo, rilascerà l'accesso in modo che il thread successivo ottenga l'accesso.

    package practice;
    import java.util.concurrent.Semaphore;

    public class SemaphoreExample {
        public static void main(String[] args) {
            Semaphore s = new Semaphore(1);
            semaphoreTask s1 = new semaphoreTask(s);
            semaphoreTask s2 = new semaphoreTask(s);
            semaphoreTask s3 = new semaphoreTask(s);
            semaphoreTask s4 = new semaphoreTask(s);
            semaphoreTask s5 = new semaphoreTask(s);
            s1.start();
            s2.start();
            s3.start();
            s4.start();
            s5.start();
        }
    }

    class semaphoreTask extends Thread {
        Semaphore s;
        public semaphoreTask(Semaphore s) {
            this.s = s;
        }
        @Override
        public void run() {
            try {
                s.acquire();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+" Going to perform some operation");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } 
    }

1

Quindi immagina che tutti stiano provando ad andare in bagno e c'è solo un certo numero di chiavi per il bagno. Ora, se non ci sono abbastanza chiavi, quella persona deve aspettare. Pensa quindi al semaforo come a quella serie di chiavi disponibili per i bagni (le risorse di sistema) a cui diversi processi (frequentatori di bagni) possono richiedere l'accesso.

Ora immagina due processi che provano ad andare in bagno contemporaneamente. Non è una buona situazione e i semafori vengono utilizzati per evitarlo. Sfortunatamente, il semaforo è un meccanismo volontario e i processi (i nostri frequentatori del bagno) possono ignorarlo (cioè anche se ci sono le chiavi, qualcuno può ancora semplicemente aprire la porta).

Ci sono anche differenze tra binari / mutex e conteggio dei semafori.

Consulta gli appunti delle lezioni su http://www.cs.columbia.edu/~jae/4118/lect/L05-ipc.html .


0

Questa è una vecchia domanda, ma uno degli usi più interessanti del semaforo è un blocco di lettura / scrittura e non è stato esplicitamente menzionato.

I lucchetti r / w funzionano in modo semplice: consumano un permesso per un lettore e tutti i permessi per gli scrittori. In effetti, un'implementazione banale di ar / w lock, ma richiede una modifica dei metadati in lettura (in realtà due volte) che può diventare un collo di bottiglia, ancora significativamente migliore di un mutex o di un lock.

Un altro aspetto negativo è che gli scrittori possono essere avviati anche abbastanza facilmente a meno che il semaforo non sia corretto o le scritture acquisiscano autorizzazioni in più richieste, in tal caso hanno bisogno di un mutex esplicito tra di loro.

Ulteriore lettura :


-3

Un semaforo è un modo per bloccare una risorsa in modo che sia garantito che mentre un pezzo di codice viene eseguito, solo questo pezzo di codice ha accesso a quella risorsa. Ciò impedisce a due thread di accedere contemporaneamente a una risorsa, il che può causare problemi.


13
suona come un mutex, non un semaforo
Sam
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.