Risposte:
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_futex
sotto 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.
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.
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.)
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.
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.
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:
Il rilascio del semaforo dovrebbe essere implementato come segue:
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.
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.
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.
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)
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'utilizzospin_trylock
richiede, 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).