Spinlock contro Semaphore


119

Quali sono le differenze fondamentali tra un semaforo e uno spin-lock?

Quando useremmo un semaforo su uno spin-lock?

Risposte:


134

Spinlock e semaforo differiscono principalmente in quattro cose:

1. Cosa sono
Uno spinlock è una possibile implementazione di un lock, vale a dire quella che viene implementata dall'attesa occupata ("spinning"). Un semaforo è una generalizzazione di un lucchetto (o, viceversa, un lucchetto è un caso speciale di un semaforo). Di solito, ma non necessariamente , gli spinlock sono validi solo all'interno di un processo, mentre i semafori possono essere utilizzati anche per la sincronizzazione tra processi diversi.

Un blocco funziona per l'esclusione reciproca, ovvero un thread alla volta può acquisire il blocco e procedere con una "sezione critica" di codice. Di solito, questo significa codice che modifica alcuni dati condivisi da più thread.
Un semaforo ha un contatore e permetterà a se stesso di essere acquisito da uno o più thread, a seconda del valore che si inserisce in esso e (in alcune implementazioni) a seconda di quale sia il suo valore massimo consentito.

Pertanto, si può considerare un blocco un caso speciale di un semaforo con un valore massimo di 1.

2. Cosa fanno
Come detto sopra, uno spinlock è un blocco, e quindi un meccanismo di mutua esclusione (rigorosamente da 1 a 1). Funziona interrogando ripetutamente e / o modificando una posizione di memoria, di solito in modo atomico. Ciò significa che l'acquisizione di uno spinlock è un'operazione "impegnativa" che probabilmente brucia i cicli della CPU per lungo tempo (forse per sempre!) Mentre in effetti non ottiene "nulla".
L'incentivo principale per un tale approccio è il fatto che un cambio di contesto ha un overhead equivalente a girare alcune centinaia (o forse mille) volte, quindi se un blocco può essere acquisito bruciando alcuni cicli di rotazione, questo potrebbe nel complesso benissimo essere più efficiente. Inoltre, per le applicazioni in tempo reale potrebbe non essere accettabile bloccare e attendere che lo scheduler torni da loro in un momento lontano in futuro.

Un semaforo, al contrario, o non gira affatto, o gira solo per un tempo molto breve (come un'ottimizzazione per evitare l'overhead di syscall). Se un semaforo non può essere acquisito, si blocca, cedendo il tempo della CPU a un thread diverso pronto per essere eseguito. Ciò può ovviamente significare che passano alcuni millisecondi prima che il thread venga nuovamente pianificato, ma se questo non è un problema (di solito non lo è), può essere un approccio molto efficiente e conservativo per la CPU.

3. Come si comportano in presenza di congestioneÈ
un malinteso comune che gli spinlock o gli algoritmi senza blocco siano "generalmente più veloci", o che siano utili solo per "compiti molto brevi" (idealmente, nessun oggetto di sincronizzazione dovrebbe essere tenuto più a lungo assolutamente necessario, mai).
L'unica differenza importante è il modo in cui i diversi approcci si comportano in presenza di congestione .

Un sistema ben progettato normalmente ha una congestione bassa o nulla (questo significa che non tutti i thread tentano di acquisire il blocco nello stesso momento). Ad esempio, normalmente non si scriverebbe codice che acquisisca un blocco, quindi carichi mezzo megabyte di dati compressi con zip dalla rete, decodifichi e analizzi i dati e infine modifichi un riferimento condiviso (aggiungi dati a un contenitore, ecc.) prima di rilasciare la serratura. Invece, si acquisirà il blocco solo allo scopo di accedere alla risorsa condivisa .
Poiché questo significa che c'è molto più lavoro all'esterno della sezione critica che al suo interno, naturalmente la probabilità che un thread si trovi all'interno della sezione critica è relativamente bassa, e quindi pochi thread si contendono il blocco allo stesso tempo. Ovviamente ogni tanto due thread cercheranno di acquisire il blocco allo stesso tempo (se ciò non potesse accadere non avresti bisogno di un blocco!), Ma questa è piuttosto l'eccezione che la regola in un sistema "sano" .

In tal caso, uno spinlock supera di gran lunga un semaforo perché se non c'è congestione del blocco, l'overhead dell'acquisizione dello spinlock è di una dozzina di cicli rispetto a centinaia / migliaia di cicli per un cambio di contesto o 10-20 milioni di cicli per perdere il resto di un intervallo di tempo.

D'altra parte, data l'elevata congestione, o se il blocco viene tenuto per lunghi periodi (a volte non puoi farci niente!), Uno spinlock brucerà quantità folli di cicli della CPU per non ottenere nulla.
Un semaforo (o mutex) è una scelta molto migliore in questo caso, poiché consente a un thread diverso di eseguire attività utili durante quel periodo. Oppure, se nessun altro thread ha qualcosa di utile da fare, consente al sistema operativo di limitare la CPU e ridurre il calore / risparmiare energia.

Inoltre, su un sistema single-core, uno spinlock sarà abbastanza inefficiente in presenza di congestione del blocco, poiché un thread rotante sprecherà tutto il suo tempo in attesa di un cambiamento di stato che non può accadere (non fino a quando il thread di rilascio non è programmato, il che non è non accade mentre il thread in attesa è in esecuzione!). Pertanto, data qualsiasi quantità di conflitto, l'acquisizione del blocco richiede circa 1 1/2 intervalli di tempo nel migliore dei casi (supponendo che il thread di rilascio sia il successivo pianificato), il che non è un comportamento molto buono.

4. Come vengono implementati
Un semaforo oggigiorno normalmente si avvolge sys_futexsotto Linux (opzionalmente con uno spinlock che esce dopo alcuni tentativi).
Uno spinlock viene tipicamente implementato utilizzando operazioni atomiche e senza utilizzare nulla fornito dal sistema operativo. In passato, ciò significava utilizzare sia gli elementi intrinseci del compilatore sia le istruzioni assembler non portabili. Nel frattempo sia C ++ 11 che C11 hanno operazioni atomiche come parte del linguaggio, quindi a parte la difficoltà generale di scrivere codice privo di blocco in modo dimostrabile corretto, è ora possibile implementare codice privo di blocchi in modo completamente portabile e (quasi) modo indolore.


"Inoltre, su un sistema single-core, uno spinlock sarà abbastanza inefficiente in presenza di congestione del blocco, poiché un thread rotante sprecherà tutto il suo tempo in attesa di un cambiamento di stato che non può accadere": c'è anche (almeno su Linux ) il spin_trylock, che ritorna immediatamente con un codice di errore, se non è stato possibile acquisire il blocco. Uno spin-lock non è sempre così duro. Ma l'utilizzo spin_trylockrichiede, per un'applicazione, di essere adeguatamente progettata in questo modo (probabilmente una coda di operazioni in sospeso, e qui, selezionare quella successiva, lasciando l'effettivo in coda).
Hibou57

Il blocco di mutex e semafori non è utile solo in ambienti a thread singolo, ma anche in caso di sottoscrizione eccessiva, ovvero il numero di thread creati da un programma (o più programmi che condividono il sistema) è superiore al numero di risorse hardware. In questi casi, il blocco del thread consente agli altri di utilizzare il tempo della CPU in modo utile. Inoltre, se l'hardware supporta l'hyperthreading, l'altro thread potrebbe utilizzare le unità di esecuzione che vengono utilizzate per eseguire il ciclo di inattività.
Jorge Bellon

76

molto semplicemente, un semaforo è un oggetto di sincronizzazione "cedevole", uno spinlock è uno "busywait". (c'è un po 'di più nei semafori in quanto sincronizzano diversi thread, a differenza di un mutex o guard o monitor o sezione critica che protegge una regione di codice da un singolo thread)

Utilizzeresti un semaforo in più circostanze, ma usa uno spinlock in cui bloccherai per un tempo molto breve: il blocco ha un costo soprattutto se blocchi molto. In questi casi può essere più efficiente eseguire lo spinlock per un po 'in attesa che la risorsa protetta venga sbloccata. Ovviamente c'è un calo delle prestazioni se giri troppo a lungo.

in genere, se giri più a lungo di un quantum di thread, dovresti usare un semaforo.


27

Oltre a quanto hanno detto Yoav Aviram e gbjbaanb, l'altro punto chiave era che non avresti mai usato uno spin-lock su una macchina a CPU singola, mentre un semaforo avrebbe senso su una macchina del genere. Al giorno d'oggi, è spesso difficile trovare una macchina senza più core, o hyperthreading, o equivalente, ma nelle circostanze in cui si dispone di una sola CPU, è necessario utilizzare i semafori. (Confido che il motivo sia ovvio. Se la singola CPU è impegnata in attesa che qualcos'altro rilasci lo spin-lock, ma è in esecuzione sull'unica CPU, è improbabile che il blocco venga rilasciato fino a quando il processo o thread corrente non viene preceduto da l'O / S, che potrebbe richiedere un po 'di tempo e non accade nulla di utile fino a quando non si verifica la prelazione.)


7
Vorrei sottolineare quanto sia importante non utilizzare spinlock su sistemi a thread singolo. Sono un problema di inversione prioritaria. E credimi: non vuoi eseguire il debug di questo tipo di bug.
Nils Pipenbrinck

2
gli spinlock sono ovunque nel kernel Linux, indipendentemente dal fatto che tu abbia una o più CPU. Cosa intendi esattamente?
Prof. Falken

@Amigable: per definizione, uno spinlock significa che il thread corrente sulla CPU è in attesa che qualcos'altro rilasci l'oggetto bloccato. Se l'unica cosa attiva che può modificare il blocco è la CPU corrente, il blocco non verrà liberato ruotando. Se qualcos'altro - un trasferimento DMA o un altro controller I / O può rilasciare il blocco, tutto bene. Ma girare quando nient'altro può rilasciare il blocco non è molto sensato: potresti anche cedere la CPU a un altro processo ora come aspettare di essere anticipati.
Jonathan Leffler

1
Potrei benissimo sbagliarmi, ma avevo l'impressione che un kernel Linux rientrante (singola CPU) potesse interrompere uno spin lock in esecuzione.
Prof.Falken

2
@Amigable: c'è la possibilità che anch'io mi sbagli, ma penso di essere vicino alla classica definizione di spinlock. Con la pianificazione preventiva, un processo potrebbe ruotare su un blocco fino alla fine del suo intervallo di tempo, o fino a quando un'interruzione non lo fa cedere, ma se un altro processo deve fornire la condizione che consente allo spinlock di bloccarsi, uno spinlock non è un buona idea su una singola macchina CPU. Il sistema su cui lavoro ha spinlock e ha un limite superiore configurabile sul numero di giri prima che entri in una modalità di attesa non occupata. Questo è uno spin-lock a livello utente; potrebbe esserci una differenza nel kernel.
Jonathan Leffler

19

Da Linux Device Drivers di Rubinni

A differenza dei semafori, gli spinlock possono essere usati nel codice che non può dormire, come i gestori di interrupt


8

Non sono un esperto di kernel ma qui ci sono alcuni punti:

Anche la macchina monoprocessore può usare spin-lock se la preemption del kernel è abilitata durante la compilazione del kernel. Se la preemption del kernel è disabilitata, allora lo spin-lock (forse) si espande in un'istruzione void .

Inoltre, quando proviamo a confrontare Semaphore vs Spin-lock, credo che il semaforo si riferisca a quello usato nel kernel, NON quello usato per IPC (userland).

Fondamentalmente, lo spin-lock deve essere utilizzato se la sezione critica è piccola (più piccola dell'overhead di sleep / wake-up) e la sezione critica non chiama nulla che possa dormire! Un semaforo deve essere utilizzato se la sezione critica è più grande e può dormire.

Raman Chalotra.


7

Spinlock si riferisce a un'implementazione del blocco inter-thread utilizzando istruzioni di assemblaggio dipendenti dalla macchina (come test-and-set). È chiamato spinlock perché il thread attende semplicemente in un ciclo ("gira") ripetutamente controllando fino a quando il blocco diventa disponibile (attesa occupata). Gli spinlock sono usati come sostituti dei mutex, che sono una funzionalità fornita dai sistemi operativi (non dalla CPU), perché gli spinlock funzionano meglio, se bloccati per un breve periodo di tempo.

Un semaforo è una struttura fornita dai sistemi operativi per IPC, quindi il suo scopo principale è la comunicazione tra processi. Essendo una funzionalità fornita dal sistema operativo, le sue prestazioni non saranno buone come quelle di uno spinlock per il blocco inter-thead (sebbene possibile). I semafori sono migliori per il blocco per periodi di tempo più lunghi.

Detto questo, l'implementazione degli splinlock in assembly è complicata e non portabile.


4
Tutte le CPU multi-threading necessitano di un'istruzione spinlock ("test and set") ed è sempre implementata come una singola istruzione nell'hardware perché altrimenti ci sarebbe sempre una condizione di competizione in cui più di un thread pensava di "possedere" la risorsa protetta.
Richard T

Non sono sicuro che tu capisca i semafori ... guarda cosa ha detto Dijkstra: cs.cf.ac.uk/Dave/C/node26.html
gbjbaanb

POSIX fa una distinzione tra un semaforo condiviso dai thread e un semaforo condiviso dai processi.
Greg Rogers

2
I semafori servono per la sincronizzazione tra processi, non per la comunicazione.
Johan Bezem

6

Vorrei aggiungere le mie osservazioni, più generali e non molto specifiche per Linux.

A seconda dell'architettura della memoria e delle capacità del processore, potrebbe essere necessario uno spin-lock per implementare un semaforo su un sistema multi-core o multiprocessore, perché in tali sistemi potrebbe verificarsi una condizione di competizione quando due o più thread / processi vogliono per acquisire un semaforo.

Sì, se la tua architettura di memoria offre il blocco di una sezione di memoria da parte di un core / processore ritardando tutti gli altri accessi e se i tuoi processori offrono un test-and-set, puoi implementare un semaforo senza spin-lock (ma con molta attenzione! ).

Tuttavia, poiché sono progettati sistemi multi-core semplici / economici (sto lavorando in sistemi embedded), non tutte le architetture di memoria supportano tali funzionalità multi-core / multiprocessore, solo test-and-set o equivalenti. Quindi un'implementazione potrebbe essere la seguente:

  • acquisire lo spin-lock (occupato in attesa)
  • prova ad acquisire il semaforo
  • rilascia lo spin-lock
  • se il semaforo non è stato acquisito con successo, sospendere il thread corrente fino al rilascio del semaforo; altrimenti continuare con la sezione critica

Il rilascio del semaforo dovrebbe essere implementato come segue:

  • acquisire lo spin-lock
  • rilascia il semaforo
  • rilascia lo spin-lock

Sì, e per semafori binari semplici a livello di sistema operativo sarebbe possibile utilizzare solo uno spin-lock in sostituzione. Ma solo se le sezioni di codice da proteggere sono davvero molto piccole.

Come detto prima, se e quando implementi il ​​tuo sistema operativo, assicurati di stare attento. Il debug di tali errori è divertente (la mia opinione, non condivisa da molti), ma soprattutto molto noioso e difficile.


1

Un "mutex" (o "blocco di mutua esclusione") è un segnale che due o più processi asincroni possono utilizzare per riservare una risorsa condivisa per uso esclusivo. Il primo processo che ottiene la proprietà del "mutex" ottiene anche la proprietà della risorsa condivisa. Altri processi devono attendere che il primo processo rilasci la sua proprietà del "mutex" prima di poter tentare di ottenerlo.

La primitiva di blocco più comune nel kernel è lo spinlock. Lo spinlock è un lucchetto a supporto singolo molto semplice. Se un processo tenta di acquisire uno spinlock e non è disponibile, il processo continuerà a provare (a girare) fino a quando non può acquisire il blocco. Questa semplicità crea una serratura piccola e veloce.


1

Spinlock viene utilizzato se e solo se si è abbastanza certi che il risultato atteso avverrà molto brevemente, prima che scada il tempo di slice di esecuzione del thread.

Esempio: nel modulo del driver del dispositivo, il driver scrive "0" nel registro hardware R0 e ora deve attendere che quel registro R0 diventi 1. L'H / W legge R0 e fa del lavoro e scrive "1" in R0. Questo è generalmente veloce (in micro secondi). Ora girare è molto meglio che andare a dormire e interrotto dall'H / W. Ovviamente, durante la rotazione, è necessario prestare attenzione alla condizione di guasto H / W!

Non c'è assolutamente alcun motivo per far girare un'applicazione utente. Non ha senso. Stai per girare perché si verifichi un evento e quell'evento deve essere completato da un'altra applicazione a livello di utente che non è mai garantito che si verifichi entro un breve lasso di tempo. Quindi, non girerò affatto in modalità utente. È meglio dormire () o mutexlock () o semaphore lock () in modalità utente.


1

Da qual è la differenza tra spin lock e semafori? di Maciej Piechotka :

Entrambi gestiscono una risorsa limitata. Descriverò prima la differenza tra il semaforo binario (mutex) e lo spin lock.

I blocchi di rotazione eseguono un'attesa impegnativa, ovvero continua a eseguire il ciclo:

while (try_acquire_resource ()); 
 ...  
pubblicazione();

Esegue un blocco / sblocco molto leggero, ma se il thread di blocco verrà preceduto da altri che tenteranno di accedere alla stessa risorsa, il secondo proverà semplicemente ad acquisire la risorsa fino a quando non esaurirà i quanti della CPU.
D'altra parte il mutex si comporta più come:

if (! try_lock ()) {
    add_to_waiting_queue ();
    aspettare();
}
...
processo * p = get_next_process_from_waiting_queue ();
p-> wakeUp ();

Quindi, se il thread tenterà di acquisire una risorsa bloccata, verrà sospeso fino a quando non sarà disponibile. Il blocco / sblocco è molto più pesante ma l'attesa è "gratuita" e "corretta".

Il semaforo è un blocco che può essere utilizzato più volte (noto dall'inizializzazione) per un numero di volte, ad esempio 3 thread possono contenere simultaneamente la risorsa ma non di più. Viene utilizzato ad esempio nel problema produttore / consumatore o in generale nelle code:

P (resources_sem)
resource = resources.pop ()
...
resources.push (risorse)
V (resources_sem)

Differenza tra semaforo, mutex e spinlock?

Blocco in Linux


1
Sembra essere un copia / incolla di questo ;-): qual è la differenza tra spin lock e semafori?
Hibou57

0

lo spin lock può essere mantenuto da un solo processo mentre il semaforo può essere mantenuto da uno o più processi. Spin lock attende che il processo rilasci un blocco e quindi acquisisca un blocco. Il semaforo sta dormendo, cioè aspetta e va a dormire.

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.