Quando si dovrebbe usare uno spinlock invece di mutex?


294

Penso che entrambi stiano facendo lo stesso lavoro, come si decide quale utilizzare per la sincronizzazione?


1
possibile duplicato di Spinlock contro il semaforo!
Paul R,

11
Mutex e Semaphore non sono la stessa cosa, quindi non penso che sia un duplicato. La risposta dell'articolo di riferimento lo afferma correttamente. Per maggiori dettagli consultare barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore
nanoquack,

Risposte:


729

La teoria

In teoria, quando un thread tenta di bloccare un mutex e non riesce, poiché il mutex è già bloccato, andrà in modalità di sospensione, consentendo immediatamente l'esecuzione di un altro thread. Continuerà a dormire fino a quando non verrà svegliato, cosa che succederà una volta sbloccato il mutex da qualunque thread tenesse il lucchetto prima. Quando un thread tenta di bloccare uno spinlock e non ha esito positivo, tenterà nuovamente di bloccarlo, fino a quando non avrà esito positivo; quindi non consentirà a un altro thread di prendere il suo posto (tuttavia, il sistema operativo passerà forzatamente a un altro thread, ovviamente una volta superato il quanto di runtime della CPU del thread corrente).

Il problema

Il problema con i mutex è che mettere i thread in modalità sleep e riattivarli sono entrambe operazioni piuttosto costose, avranno bisogno di un bel po 'di istruzioni sulla CPU e quindi impiegheranno anche del tempo. Se ora il mutex è stato bloccato solo per un periodo di tempo molto breve, il tempo trascorso a mettere in sospensione un thread e a risvegliarlo potrebbe superare il tempo in cui il thread ha effettivamente dormito di gran lunga e potrebbe anche superare il tempo in cui il thread sarebbe hanno sprecato sondando costantemente su uno spinlock. D'altra parte, il polling su uno spinlock perderà costantemente il tempo della CPU e se il blocco viene mantenuto per un periodo di tempo più lungo, questo perderà molto più tempo della CPU e sarebbe invece molto meglio se il thread dormisse invece.

La soluzione

L'uso di spinlock su un sistema single-core / single-CPU di solito non ha senso, dal momento che il polling degli spinlock blocca l'unico core della CPU disponibile, nessun altro thread può essere eseguito e poiché nessun altro thread può essere eseguito, il lock non funzionerà sia sbloccato neanche. IOW, uno spinlock spreca solo tempo CPU su quei sistemi senza alcun vantaggio reale. Se invece il thread fosse stato sospeso, un altro thread avrebbe potuto essere eseguito contemporaneamente, probabilmente sbloccando il blocco e consentendo quindi al primo thread di continuare l'elaborazione, una volta che si è svegliato di nuovo.

Su un sistema multi-core / multi-CPU, con molti blocchi che vengono trattenuti solo per un periodo di tempo molto breve, il tempo sprecato per mettere costantemente in sospensione i thread e riattivarli potrebbe ridurre notevolmente le prestazioni di runtime. Quando si utilizzano invece spinlock, i thread hanno la possibilità di sfruttare il loro quantum di runtime completo (bloccando sempre solo per un periodo di tempo molto breve, ma poi continuano immediatamente il loro lavoro), portando a un throughput di elaborazione molto più elevato.

La pratica

Poiché molto spesso i programmatori non possono sapere in anticipo se i mutex o gli spinlock saranno migliori (ad es. Perché il numero di core della CPU dell'architettura di destinazione è sconosciuto), né i sistemi operativi possono sapere se un determinato codice è stato ottimizzato per single-core o ambienti multi-core, la maggior parte dei sistemi non fa una distinzione rigorosa tra mutex e spinlock. In effetti, la maggior parte dei sistemi operativi moderni ha mutex ibridi e spinlock ibridi. Cosa significa veramente?

Un mutex ibrido si comporta inizialmente come uno spinlock su un sistema multi-core. Se un thread non è in grado di bloccare il mutex, non verrà messo immediatamente in sospensione, poiché il mutex potrebbe sbloccarsi molto presto, quindi il mutex si comporterà dapprima esattamente come uno spinlock. Solo se il blocco non è ancora stato ottenuto dopo un certo periodo di tempo (o tentativi o qualsiasi altro fattore di misurazione), il thread viene effettivamente messo in sospensione. Se lo stesso codice viene eseguito su un sistema con un solo core, il mutex non si bloccherà, anche se, come sopra, non sarebbe utile.

All'inizio uno spinlock ibrido si comporta come un normale spinlock, ma per evitare di sprecare troppo tempo della CPU, potrebbe avere una strategia di back-off. Di solito non metterà in pausa il thread (poiché non si desidera che ciò accada quando si utilizza uno spinlock), ma potrebbe decidere di interrompere il thread (immediatamente o dopo un determinato periodo di tempo) e consentire l'esecuzione di un altro thread , aumentando così le possibilità che lo spinlock sia sbloccato (uno switch di thread puro è generalmente meno costoso di uno che comporta la sospensione di un thread e la sua riattivazione in seguito, anche se non di gran lunga).

Sommario

In caso di dubbio, utilizzare i mutex, di solito sono la scelta migliore e la maggior parte dei sistemi moderni consentirà loro di girare per un periodo di tempo molto breve, se questo sembra vantaggioso. L'uso degli spinlock a volte può migliorare le prestazioni, ma solo in determinate condizioni e il fatto che tu sia in dubbio mi dice, che attualmente non stai lavorando a nessun progetto in cui uno spinlock potrebbe essere utile. Potresti prendere in considerazione l'utilizzo del tuo "oggetto blocco", che può utilizzare internamente uno spinlock o un mutex (ad esempio, questo comportamento potrebbe essere configurabile durante la creazione di tale oggetto), inizialmente usa i mutex ovunque e se pensi che l'uso di uno spinlock da qualche parte potrebbe davvero aiuto, provalo e confronta i risultati (ad es. usando un profiler), ma assicurati di testare entrambi i casi,

Aggiornamento: un avviso per iOS

In realtà non specifico di iOS, ma iOS è la piattaforma in cui la maggior parte degli sviluppatori può affrontare questo problema: se il tuo sistema ha un programmatore di thread, ciò non garantisce che qualsiasi thread, indipendentemente dalla sua priorità, possa eventualmente avere la possibilità di funzionare, quindi gli spinlock possono portare a deadlock permanenti. Lo scheduler iOS distingue diverse classi di thread e thread su una classe inferiore verranno eseguiti solo se nessun thread in una classe superiore vuole eseguire anche. Non esiste una strategia di back-off per questo, quindi se hai permanentemente disponibili thread di alta classe, i thread di bassa classe non avranno mai tempo di CPU e quindi nessuna possibilità di eseguire alcun lavoro.

Il problema si presenta come segue: Il tuo codice ottiene uno spinlock in un thread di classe prio basso e mentre si trova nel mezzo di quel lock, il tempo quantico ha superato e il thread smette di funzionare. L'unico modo in cui questo spinlock può essere rilasciato di nuovo è se quel thread di bassa classe prio ottiene di nuovo il tempo della CPU ma questo non è garantito. Potresti avere un paio di thread di alta classe prio che vogliono costantemente essere eseguiti e l'utilità di pianificazione darà sempre la priorità a quelli. Uno di essi può imbattersi nello spinlock e tentare di ottenerlo, il che ovviamente non è possibile, e il sistema lo farà fruttare. Il problema è: un thread ceduto è immediatamente disponibile per essere eseguito di nuovo! Avendo un prio più alto del thread che tiene il blocco, il thread che tiene il blocco non ha alcuna possibilità di ottenere il runtime della CPU.

Perché questo problema non si verifica con i mutex? Quando il thread prio alto non riesce a ottenere il mutex, non cederà, potrebbe girare un po 'ma alla fine verrà inviato in modalità sleep. Un thread inattivo non è disponibile per l'esecuzione finché non viene svegliato da un evento, ad esempio un evento come il mutex che viene sbloccato che stava aspettando. Apple è a conoscenza di questo problema e di conseguenza si è deprezzata OSSpinLock. Viene chiamato il nuovo blocco os_unfair_lock. Questo blocco evita la situazione sopra menzionata poiché è a conoscenza delle diverse classi di priorità dei thread. Se sei sicuro che l'utilizzo di spinlock sia una buona idea nel tuo progetto iOS, usa quello. Stare lontano daOSSpinLock! E in nessun caso implementa i tuoi spinlock in iOS! In caso di dubbio, utilizzare un mutex! macOS non è interessato da questo problema in quanto ha un programma di pianificazione thread diverso che non consentirà a qualsiasi thread (anche thread prio bassi) di "funzionare a secco" nel tempo della CPU, tuttavia la stessa situazione può sorgere lì e quindi portare a un livello molto scarso le prestazioni, quindi OSSpinLocksono deprecate anche su macOS.


3
spiegazione superba ... ho dei dubbi su spinlock i, posso usare uno spinlock in ISR? se no perché no
haris

4
@Mecki Se non sbaglio, credo che tu abbia suggerito nella tua risposta che l'intervallo di tempo si verifica solo su sistemi a processore singolo. Questo non è corretto! È possibile utilizzare un blocco spin su un sistema a singolo processore e ruoterà fino a quando non scade il suo tempo quantico. Quindi può subentrare un altro thread con la stessa priorità (proprio come quello descritto per i sistemi multiprocessore).
fumoboy007

7
@ fumoboy007 "e girerà fino allo scadere del suo tempo quantico" // Il che significa che sprechi tempo CPU / potenza della batteria per assolutamente nulla senza alcun singolo vantaggio, il che è assolutamente idiota. E no, da nessuna parte ho detto che il time slicing si verifica solo su sistemi single core, ho detto che sui sistemi single core esiste SOLO time slicing, mentre c'è REAL parallelismo su sistemi multicore (e anche time slicing, ma anche time slicing, ma irrilevante per ciò che ho scritto nel mio rispondere); inoltre hai completamente perso il punto su cosa sia uno spinlock ibrido e perché funzioni bene su sistemi single e multicore.
Mecki,

11
@ fumoboy007 Il thread A contiene il blocco e viene interrotto. Il thread B viene eseguito e desidera il blocco, ma non può ottenerlo, quindi gira. Su un sistema multicore, il thread A può continuare a funzionare su un altro core mentre il thread B sta ancora girando, rilasciare il blocco e il thread B può continuare nel suo tempo quantico attuale. Su un sistema single core, esiste un solo thread A che può essere eseguito per rilasciare il blocco e questo core è occupato dalla filatura del thread B. Quindi non è mai possibile rilasciare lo spinlock prima che Thread B superi il suo tempo quantico e quindi tutto lo spin è solo una perdita di tempo.
Mecki,

2
Se vuoi saperne di più su spinlock e mutex implementati nel kernel Linux, ti consiglio vivamente di leggere il capitolo 5 dei grandi driver di dispositivo Linux, Third Edition (LDD3) (mutex: pagina 109; spinlock: pagina 116).
patryk.beza,

7

Continuando con il suggerimento di Mecki, questo articolo pthread mutex vs pthread spinlock sul blog di Alexander Sandler, Alex su Linux mostra come spinlocke mutexespuò essere implementato per testare il comportamento usando #ifdef.

Tuttavia, assicurati di prendere la chiamata finale in base alla tua osservazione, comprendendo che l'esempio fornito è un caso isolato, i requisiti del tuo progetto, l'ambiente potrebbe essere completamente diverso.


6

Si noti inoltre che in determinati ambienti e condizioni (come l'esecuzione su Windows a livello di spedizione> = DISPATCH LEVEL), non è possibile utilizzare mutex ma piuttosto spinlock. Su unix - stessa cosa.

Ecco una domanda equivalente sul sito unix dello stackexchange della concorrenza: /unix/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something- Di Più

Informazioni sulla spedizione su sistemi Windows: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc


6

La risposta di Mecki la risolve abbastanza bene. Tuttavia, su un singolo processore, l'utilizzo di uno spinlock potrebbe avere senso quando l'attività è in attesa sul blocco per essere fornita da una routine di servizio di interruzione. L'interruzione trasferirà il controllo all'ISR, che preparerebbe la risorsa per l'uso da parte dell'attività in attesa. Si finirebbe rilasciando il blocco prima di restituire il controllo all'attività interrotta. L'attività di filatura trova lo spinlock disponibile e procede.


2
Non sono sicuro di essere pienamente d'accordo con questa risposta. Uno un singolo processore, se un'attività contiene un blocco su una risorsa, allora l'ISR non può procedere in sicurezza e non può attendere che l'attività sblocchi la risorsa (poiché l'attività che contiene la risorsa è stata interrotta). In tali casi, l'attività dovrebbe semplicemente disabilitare gli interrupt per imporre l'esclusione tra se stesso e l'ISR. Naturalmente, questo dovrebbe essere fatto per intervalli di tempo molto brevi.
user1202136

3

I meccanismi di sincronizzazione Spinlock e Mutex sono oggi molto comuni da vedere.

Pensiamo prima a Spinlock.

Fondamentalmente si tratta di un'azione di attesa impegnata, il che significa che dobbiamo attendere il rilascio di un blocco specificato prima di poter procedere con l'azione successiva. Concettualmente molto semplice, mentre lo si implementa non è sul caso. Ad esempio: se il blocco non è stato rilasciato, il thread è stato scambiato ed entrare nello stato di sospensione, dovremmo occuparcene? Come gestire i blocchi di sincronizzazione quando due thread richiedono contemporaneamente l'accesso?

In generale, l'idea più intuitiva riguarda la sincronizzazione tramite una variabile per proteggere la sezione critica. Il concetto di Mutex è simile, ma sono ancora diversi. Focus su: utilizzo della CPU. Spinlock consuma il tempo della CPU per attendere l'esecuzione dell'azione e, quindi, possiamo riassumere la differenza tra i due:

In ambienti multi-core omogenei, se il tempo dedicato alla sezione critica è ridotto rispetto all'uso di Spinlock, poiché è possibile ridurre il tempo di commutazione del contesto. (Il confronto single-core non è importante, poiché alcuni sistemi implementano Spinlock nel mezzo dello switch)

In Windows, l'utilizzo di Spinlock aggiornerà il thread su DISPATCH_LEVEL, che in alcuni casi potrebbe non essere consentito, quindi questa volta abbiamo dovuto utilizzare un Mutex (APC_LEVEL).


-6

L'uso di spinlock su un sistema single-core / single-CPU di solito non ha senso, dal momento che il polling degli spinlock blocca l'unico core della CPU disponibile, nessun altro thread può essere eseguito e poiché nessun altro thread può essere eseguito, il lock non funzionerà sia sbloccato neanche. IOW, uno spinlock spreca solo tempo CPU su quei sistemi senza alcun vantaggio reale

Questo è sbagliato. Non vi è alcuno spreco di cicli di CPU nell'uso di spinlock su sistemi di processori uni, perché una volta che un processo prende un blocco di spin, la prelazione è disabilitata, quindi, in quanto tale, non potrebbe esserci nessun altro spin! È solo che usarlo non ha alcun senso! Quindi, gli spinlock sui sistemi Uni sono sostituiti da preempt_disable in fase di compilazione dal kernel!


Il citato è ancora del tutto vero. Se il risultato compilato del codice sorgente non contiene spinlock, allora il citato è irrilevante. Supponendo che ciò che hai detto sia vero riguardo al kernel che sostituisce gli spinlock al momento della compilazione, come vengono gestiti gli spinlock quando vengono precompilati su un'altra macchina che può essere o meno uniprocessore, a meno che non stiamo parlando strettamente di spinlock solo nel kernel stesso?
Hydranix

"Una volta che un processo ha un blocco di rotazione, la prelazione è disabilitata". La prevenzione non è disabilitata quando un processo esegue una rotazione. In tal caso, un singolo processo potrebbe abbattere l'intera macchina semplicemente inserendo uno spinlock e non uscendo mai. Si noti che se il thread viene eseguito nello spazio del kernel (anziché nello spazio utente), l'esecuzione di un blocco spin disabilita effettivamente la preemption, ma non penso che sia ciò di cui si sta discutendo qui.
Konstantin Weitz,

In fase di compilazione da parte del kernel ?
Shien,

@konstantin FYI spin lock può essere preso solo nello spazio del kernel. E quando viene eseguito uno spin lock, la preemption viene disabilitata sul processore locale.
Neelansh Mittal,

@hydranix Non ti è arrivato? Ovviamente non puoi compilare un modulo su un kernel in cui CONFIG_SMP è abilitato ed eseguire lo stesso modulo su un kernel in cui CONFIG_SMP è disabilitato.
Neelansh Mittal,
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.