Variabile condizionale vs semaforo


Risposte:


207

Le serrature vengono utilizzate per l'esclusione reciproca. Quando vuoi assicurarti che un pezzo di codice sia atomico, mettici un lucchetto intorno. In teoria potresti usare un semaforo binario per farlo, ma questo è un caso speciale.

I semafori e le variabili di condizione si basano sull'esclusione reciproca fornita dai blocchi e vengono utilizzati per fornire un accesso sincronizzato alle risorse condivise. Possono essere utilizzati per scopi simili.

Una variabile di condizione viene generalmente utilizzata per evitare un'attesa occupata (ripetutamente ripetuta durante il controllo di una condizione) durante l'attesa che una risorsa diventi disponibile. Ad esempio, se hai un thread (o più thread) che non può continuare fino a quando una coda non è vuota, l'approccio di attesa occupata sarebbe semplicemente fare qualcosa come:

//pseudocode
while(!queue.empty())
{
   sleep(1);
}

Il problema con questo è che stai sprecando tempo del processore facendo controllare ripetutamente la condizione da questo thread. Perché invece non avere una variabile di sincronizzazione che può essere segnalata per dire al thread che la risorsa è disponibile?

//pseudocode
syncVar.lock.acquire();

while(!queue.empty())
{
   syncVar.wait();
}

//do stuff with queue

syncVar.lock.release();

Presumibilmente, avrai un thread da qualche altra parte che sta tirando le cose fuori dalla coda. Quando la coda è vuota, può chiamare syncVar.signal()per svegliare un thread casuale su cui è seduto addormentato syncVar.wait()(o di solito c'è anche un metodo signalAll()o broadcast()per svegliare tutti i thread che sono in attesa).

In genere uso variabili di sincronizzazione come questa quando ho uno o più thread in attesa di una singola condizione particolare (ad esempio, che la coda sia vuota).

I semafori possono essere usati in modo simile, ma penso che siano usati meglio quando hai una risorsa condivisa che può essere disponibile e non disponibile in base a un numero intero di cose disponibili. I semafori sono utili per le situazioni produttore / consumatore in cui i produttori allocano risorse e i consumatori le consumano.

Pensa se avessi un distributore automatico di bibite. C'è solo una macchina per bibite ed è una risorsa condivisa. Hai un thread che è un venditore (produttore) che è responsabile di mantenere la macchina rifornita e N thread che sono acquirenti (consumatori) che vogliono ottenere bibite dalla macchina. Il numero di bibite nella macchina è il valore intero che guiderà il nostro semaforo.

Ogni thread acquirente (consumatore) che arriva alla macchina della soda chiama il down()metodo del semaforo per prendere una soda. Questo prenderà una soda dalla macchina e decrementerà il conteggio delle bibite disponibili di 1. Se sono disponibili bibite, il codice continuerà a scorrere oltre l' down()istruzione senza problemi. Se non sono disponibili bibite gassate, il thread dormirà qui in attesa di essere avvisato di quando la soda sarà nuovamente disponibile (quando ci sono più bibite nella macchina).

Il thread del venditore (produttore) starebbe essenzialmente aspettando che il distributore di bibite sia vuoto. Il venditore viene avvisato quando l'ultima soda viene prelevata dalla macchina (e uno o più consumatori sono potenzialmente in attesa di estrarre le bibite). Il venditore rifornirebbe la macchina della soda con il up()metodo del semaforo , il numero disponibile di bibite verrebbe incrementato ogni volta e quindi i thread dei consumatori in attesa riceverebbero una notifica che è disponibile più soda.

I metodi wait()e signal()di una variabile di sincronizzazione tendono a essere nascosti all'interno delle operazioni down()e up()del semaforo.

Certamente c'è una sovrapposizione tra le due scelte. Esistono molti scenari in cui un semaforo o una variabile di condizione (o un insieme di variabili di condizione) potrebbero entrambi servire ai tuoi scopi. Sia i semafori che le variabili di condizione sono associati a un oggetto lock che usano per mantenere l'esclusione reciproca, ma poi forniscono funzionalità extra oltre al lock per sincronizzare l'esecuzione del thread. Spetta principalmente a te capire quale ha più senso per la tua situazione.

Non è necessariamente la descrizione più tecnica, ma è così che ha senso nella mia testa.


9
Ottima risposta, vorrei aggiungere da altre risposte così: Il semaforo viene utilizzato per controllare il numero di thread in esecuzione. Ci sarà un insieme fisso di risorse. Il conteggio delle risorse verrà diminuito ogni volta che un thread possiede lo stesso. Quando il conteggio del semaforo raggiunge 0, nessun altro thread può acquisire la risorsa. I thread vengono bloccati fino a quando altri thread possiedono i rilasci di risorse. In breve, la differenza principale è quanti thread sono autorizzati ad acquisire la risorsa contemporaneamente? Mutex: il suo ONE. Semaphore - its DEFINED_COUNT, (as many as semaphore count)
berkay

10
Solo per approfondire il motivo per cui c'è questo ciclo while invece di un semplice if: qualcosa chiamato spurios wakeup . Citando questo articolo di wikipedia : "Uno dei motivi è un risveglio spurio; cioè, un thread potrebbe essere risvegliato dal suo stato di attesa anche se nessun thread ha segnalato la variabile di condizione"
Vladislavs Burakovs

3
@VladislavsBurakovs Buon punto! Penso che sia utile anche nel caso in cui una trasmissione riattivi più thread di quante siano le risorse disponibili (ad esempio, la trasmissione risveglia 3 thread, ma ci sono solo 2 elementi in coda).
Brent scrive il codice

Vorrei dare un voto positivo alla tua risposta fino a quando la coda non è piena;) Risposta perfetta. Questo codice potrebbe aiutare a capire i semafori csc.villanova.edu/~mdamian/threads/PC.htm
Mohamad-Jaafar NEHME

3
@VladislavsBurakovs Per chiarire un po ', il motivo per cui la condizione potrebbe essere ancora falsa per un thread che si è appena svegliato (risultando in un risveglio spurio) è che potrebbe esserci stato un cambio di contesto prima che il thread avesse la possibilità di controllare la condizione di nuovo, dove qualche altro thread pianificato ha reso falsa quella condizione. Questo è uno dei motivi per cui so per un risveglio spurio, non so se ce ne sono altri.
Max

52

Riveliamo cosa c'è sotto il cofano.

La variabile condizionale è essenzialmente una coda di attesa , che supporta le operazioni di attesa di blocco e di riattivazione, ovvero è possibile inserire un thread nella coda di attesa e impostare il suo stato su BLOCK, quindi estrarne un thread e impostarne lo stato su READY.

Nota che per usare una variabile condizionale, sono necessari altri due elementi:

  • una condizione (tipicamente implementata controllando un flag o un contatore)
  • un mutex che protegge la condizione

Il protocollo quindi diventa,

  1. acquisire mutex
  2. controllare le condizioni
  3. blocca e rilascia mutex se la condizione è vera, altrimenti rilascia mutex

Il semaforo è essenzialmente un contatore + un mutex + una coda di attesa. E può essere utilizzato così com'è senza dipendenze esterne. Puoi usarlo come mutex o come variabile condizionale.

Pertanto, il semaforo può essere trattato come una struttura più sofisticata rispetto alla variabile condizionale, mentre quest'ultima è più leggera e flessibile.


mutex può essere visto come una variabile di condizioni, la sua condizione è se essere mantenuto o meno.
宏杰 李

18

I semafori possono essere utilizzati per implementare l'accesso esclusivo alle variabili, tuttavia devono essere utilizzati per la sincronizzazione. I mutex, invece, hanno una semantica strettamente correlata alla mutua esclusione: solo il processo che ha bloccato la risorsa può sbloccarla.

Sfortunatamente non puoi implementare la sincronizzazione con i mutex, ecco perché abbiamo variabili di condizione. Si noti inoltre che con le variabili di condizione è possibile sbloccare tutti i thread in attesa nello stesso istante utilizzando lo sblocco della trasmissione. Questo non può essere fatto con i semafori.


9

le variabili semaforo e condizione sono molto simili e vengono utilizzate principalmente per gli stessi scopi. Tuttavia, ci sono piccole differenze che potrebbero renderne uno preferibile. Ad esempio, per implementare la sincronizzazione della barriera non saresti in grado di utilizzare un semaforo, ma una variabile di condizione è l'ideale.

La sincronizzazione della barriera è quando vuoi che tutti i tuoi thread aspettino fino a quando tutti sono arrivati ​​a una certa parte nella funzione thread. questo può essere implementato avendo una variabile statica che è inizialmente il valore dei thread totali decrementato da ogni thread quando raggiunge quella barriera. questo significherebbe che vogliamo che ogni thread dorma fino all'arrivo dell'ultimo, un semaforo farebbe l'esatto contrario! con un semaforo, ogni thread continuerà a funzionare e l'ultimo thread (che imposterà il valore del semaforo a 0) andrà a dormire.

una condizione variabile d'altra parte, è l'ideale. quando ogni thread arriva alla barriera controlliamo se il nostro contatore statico è zero. in caso contrario, impostiamo il thread in sleep con la funzione wait della variabile di condizione. quando l'ultimo thread arriva alla barriera, il valore del contatore verrà decrementato a zero e quest'ultimo chiamerà la funzione segnale della variabile di condizione che sveglierà tutti gli altri thread!


1

File le variabili di condizione sotto la sincronizzazione del monitor. In genere ho visto semafori e monitor come due diversi stili di sincronizzazione. Ci sono differenze tra i due in termini di quantità di dati di stato intrinsecamente conservati e di come si desidera modellare il codice, ma in realtà non c'è alcun problema che possa essere risolto da uno ma non dall'altro.

Tendo a codificare per monitorare la forma; nella maggior parte dei linguaggi in cui lavoro si tratta di mutex, variabili di condizione e alcune variabili di stato di supporto. Ma anche i semafori farebbero il lavoro.


2
Questa sarebbe una risposta migliore se spiegassi cos'è la "forma del monitor".
Steven Lu

0

I mutexe conditional variablessono ereditati da semaphore.

  • Per mutex, semaphoreutilizza due stati: 0, 1
  • Per condition variablesgli semaphore usi da banco.

Sono come lo zucchero sintattico


Nella libreria C ++ std sono tutti oggetti distrettuali, tutti implementati utilizzando API specifiche della piattaforma. Certamente un semaforo sbloccherà il numero di volte segnalato, la variabile di condizione potrebbe essere segnalata più volte ma si sblocca solo una volta. Questo è il motivo per cui il wair prende un mutex come parametro.
doron
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.