Quando si dovrebbe usare uno spinlock al posto del mutex?

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

La teoria

In teoria, quando un thread tenta di bloccare un mutex e non riesce, poiché il mutex è già bloccato, andrà in stop, consentendo immediatamente l’esecuzione di un altro thread. Continuerà a dormire fino a quando non verrà svegliato, il che sarà il caso una volta che il mutex verrà sbloccato da qualunque filo abbia tenuto il lucchetto in precedenza. Quando un thread tenta di bloccare uno spinlock e non ha esito positivo, continuerà a provare nuovamente a bloccarlo, fino a quando non avrà esito positivo; quindi non permetterà ad un altro thread di prendere il suo posto (tuttavia, il sistema operativo passerà forzatamente ad un altro thread, una volta che il quantum di runtime della CPU del thread corrente è stato superato, ovviamente).

Il problema

Il problema con i mutex è che mettere i thread a dormire e svegliarli di nuovo sono operazioni alquanto costose, avranno bisogno di un bel po ‘di istruzioni per la CPU e quindi anche un po’ di tempo. Se ora il mutex è stato bloccato solo per un periodo di tempo molto breve, il tempo trascorso nel mettere un thread in sleep e risvegliarlo di nuovo potrebbe superare il tempo in cui il thread ha effettivamente dormito di molto e potrebbe persino superare il tempo richiesto dal thread hanno sprecato sondando costantemente su uno spinlock. D’altra parte, il polling su uno spinlock sprecherà costantemente il tempo della CPU e se il blocco viene tenuto per un periodo di tempo più lungo, questo sprecherà molto più tempo della CPU e sarebbe stato molto meglio se il thread stesse dormendo.

La soluzione

L’uso di spinlock su un sistema single-core / single-CPU non ha senso, dal momento che il polling spinlock blocca l’unico core CPU disponibile, nessun altro thread può essere eseguito e poiché nessun altro thread può essere eseguito, il lock non lo farà sia sbloccato. IOW, uno spinlock spreca solo il tempo di CPU su quei sistemi senza alcun beneficio reale. Se invece il thread è stato messo in sleep, un altro thread avrebbe potuto essere eseguito in una sola volta, probabilmente sbloccando il blocco e consentendo quindi al primo thread di continuare l’elaborazione, una volta risvegliato.

Su un sistema multi-core / multi-CPU, con un sacco di blocchi che vengono mantenuti solo per un breve periodo di tempo, il tempo sprecato per mettere costantemente i thread in stop e risvegliarli di nuovo potrebbe ridurre sensibilmente le prestazioni di runtime. Quando si utilizzano gli spinlock, invece, i thread hanno la possibilità di sfruttare il loro pieno tempo di esecuzione (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ù alto.

La pratica

Poiché molto spesso i programmatori non possono sapere in anticipo se mutex o spinlock saranno migliori (ad esempio perché il numero di core CPU dell’architettura di destinazione è sconosciuto), né i sistemi operativi possono sapere se una determinata parte di codice è stata ottimizzata per single-core o ambienti multi-core, la maggior parte dei sistemi non distingue rigorosamente tra mutex e spinlock. In effetti, i sistemi operativi più moderni hanno mutex ibridi e spinlock ibridi. Cosa significa in realtà?

Un mutex ibrido si comporta come uno spinlock in un primo momento su un sistema multi-core. Se un thread non può bloccare il mutex, non verrà messo immediatamente in sleep, poiché il mutex potrebbe essere sbloccato molto presto, quindi il mutex si comporterà esattamente come uno spinlock. Solo se il blocco non è stato ancora ottenuto dopo un certo periodo di tempo (o tentativi o qualsiasi altro fattore di misurazione), il thread è davvero messo a dormire. Se lo stesso codice viene eseguito su un sistema con un solo core, il mutex non eseguirà spinlock, anche se, come sopra, non sarebbe vantaggioso.

All’inizio uno spinlock ibrido si comporta come un normale spinlock, ma per evitare di sprecare troppa CPU, potrebbe avere una strategia di backoff. Solitamente non mette il thread in sleep (dato che non vuoi che ciò accada quando usi uno spinlock), ma può decidere di interrompere il thread (immediatamente o dopo un certo periodo di tempo) e consentire l’esecuzione di un altro thread , aumentando così le probabilità che lo spinlock sia sbloccato (un interruttore di puro filo è solitamente meno costoso di uno che comporta il mettere un filo in sospensione e svegliarlo di nuovo in seguito, anche se non di gran lunga).

Sommario

In caso di dubbio, utilizzare i mutex, di solito sono la scelta migliore e i sistemi più moderni consentiranno loro di eseguire lo spinlock per un periodo di tempo molto breve, se ciò sembra vantaggioso. L’uso degli spinlock può a volte migliorare le prestazioni, ma solo in determinate condizioni e il fatto che tu sia in dubbio mi dice piuttosto che non stai lavorando a nessun progetto al momento in cui uno spinlock potrebbe essere utile. Potresti considerare di usare il tuo “object di blocco”, che può usare uno spinlock o un mutex internamente (ad esempio questo comportamento potrebbe essere configurabile quando si crea un tale object), inizialmente usare i mutex ovunque e se pensi che usare uno spinlock da qualche parte potrebbe davvero aiuto, provalo e confronta i risultati (es. usando un profiler), ma assicurati di testare entrambi i casi, un sistema single-core e multi-core prima di saltare alle conclusioni (e possibilmente a diversi sistemi operativi, se il tuo codice sarà multipiattaforma).

Continuando con il suggerimento di Mecki, questo articolo pthread mutex vs pthread spinlock sul blog di Alexander Sandler, Alex su Linux mostra come lo spinlock e i mutexes possono essere implementati per testare il comportamento usando #ifdef.

Tuttavia, assicurati di prendere l’ultima chiamata in base alla tua osservazione, capendo che l’esempio dato è un caso isolato, i requisiti del tuo progetto, l’ambiente potrebbe essere completamente diverso.

Si noti inoltre che in determinati ambienti e condizioni (come l’esecuzione su Windows a livello di invio> = LIVELLO DISPATCH), non è ansible utilizzare il mutex, ma piuttosto lo spinlock. Su unix – stessa cosa.

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

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

La risposta di Mecki è piuttosto buona. Tuttavia, su un singolo processore, l’uso di uno spinlock potrebbe avere senso quando l’attività è in attesa del blocco da parte di una routine di servizio di interrupt. L’interrupt avrebbe trasferito il controllo all’ISR, che avrebbe preparato la risorsa per l’utilizzo da parte del task di attesa. Termina rilasciando il blocco prima di restituire il controllo al compito interrotto. Il compito di rotazione dovrebbe trovare lo spinlock disponibile e procedere.

I meccanismi di sincronizzazione di Spinlock e Mutex sono molto comuni oggi per essere visti.

Pensiamo prima a Spinlock.

Fondamentalmente si tratta di un’azione di attesa impegnata, il che significa che dobbiamo aspettare che venga rilasciato un blocco specifico prima di poter procedere con l’azione successiva. Concettualmente molto semplice, mentre implementarlo non è sul caso. Ad esempio: se il blocco non è stato rilasciato, il thread è stato scambiato ed è entrato nello stato di sospensione, dovremmo occuparci di esso? Come gestire i blocchi di sincronizzazione quando due thread richiedono simultaneamente 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’azione, e quindi possiamo riassumere la differenza tra i due:

In ambienti multi-core omogenei, se il tempo speso per la sezione critica è ridotto rispetto all’utilizzo di Spinlock, poiché è ansible ridurre il tempo di commutazione del contesto. (Il confronto single-core non è importante, perché alcuni sistemi implementano Spinlock nel mezzo dell’interruttore)

In Windows, utilizzando Spinlock si aggiorna il thread a DISPATCH_LEVEL, che in alcuni casi potrebbe non essere consentito, quindi questa volta abbiamo dovuto utilizzare un Mutex (APC_LEVEL).

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

Questo è sbagliato. Non ci sono sprechi di cicli cpu nell’uso di spinlock su sistemi con processore uni, perché una volta che un processo ha un blocco di selezione, la prelazione è disabilitata, quindi non ci può essere nessun altro che gira! È solo che usarlo non ha senso! Quindi, spinlock su sistemi Uni sono sostituiti da preempt_disable in fase di compilazione dal kernel!