Penso che entrambi stiano facendo lo stesso lavoro, come si decide quale utilizzare per la sincronizzazione?
Penso che entrambi stiano facendo lo stesso lavoro, come si decide quale utilizzare per la sincronizzazione?
Risposte:
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,
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 OSSpinLock
sono deprecate anche su macOS.
Continuando con il suggerimento di Mecki, questo articolo pthread mutex vs pthread spinlock sul blog di Alexander Sandler, Alex su Linux mostra come spinlock
e mutexes
può 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.
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
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.
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).
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!