Volevo sapere la stessa cosa, quindi l'ho misurata. Sulla mia scatola (processore a otto core AMD FX (tm) -8150 a 3,612361 GHz), il blocco e lo sblocco di un mutex sbloccato che si trova nella sua linea di cache ed è già memorizzato nella cache, richiede 47 clock (13 ns).
A causa della sincronizzazione tra due core (ho usato CPU # 0 e # 1), ho potuto chiamare una coppia di blocco / sblocco solo una volta ogni 102 ns su due thread, quindi una volta ogni 51 ns, da cui si può concludere che ci vogliono circa 38 ns per recuperare dopo che un thread ha sbloccato prima che il thread successivo possa bloccarlo di nuovo.
Il programma che ho usato per indagare su questo può essere trovato qui:
https://github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39f41809d3e3de2f9e4b8/src/mutex_test.cxx
Nota che ha alcuni valori hardcoded specifici per il mio box (xrange, yrange e rdtsc overhead), quindi probabilmente dovrai sperimentare prima che funzioni per te.
Il grafico che produce in quello stato è:
Ciò mostra il risultato delle esecuzioni di benchmark sul seguente codice:
uint64_t do_Ndec(int thread, int loop_count)
{
uint64_t start;
uint64_t end;
int __d0;
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (start) : : "%rdx");
mutex.lock();
mutex.unlock();
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (end) : : "%rdx");
asm volatile ("\n1:\n\tdecl %%ecx\n\tjnz 1b" : "=c" (__d0) : "c" (loop_count - thread) : "cc");
return end - start;
}
Le due chiamate rdtsc misurano il numero di orologi necessari per bloccare e sbloccare `mutex '(con un overhead di 39 orologi per le chiamate rdtsc sulla mia scatola). Il terzo asm è un loop di ritardo. La dimensione del loop di ritardo è inferiore di 1 conteggio per il thread 1 rispetto a quella per il thread 0, quindi il thread 1 è leggermente più veloce.
La funzione sopra è chiamata in un loop stretto di dimensioni 100.000. Nonostante la funzione sia leggermente più veloce per il thread 1, entrambi i loop si sincronizzano a causa della chiamata al mutex. Ciò è visibile nel grafico dal fatto che il numero di orologi misurati per la coppia di blocco / sblocco è leggermente maggiore per il thread 1, per tenere conto del ritardo più breve nel loop sottostante.
Nel grafico sopra il punto in basso a destra è una misura con un ritardo loop_count di 150, e quindi seguendo i punti in basso, verso sinistra, il loop_count viene ridotto di uno ogni misura. Quando diventa 77 la funzione viene chiamata ogni 102 ns in entrambi i thread. Se successivamente loop_count viene ulteriormente ridotto, non è più possibile sincronizzare i thread e il mutex inizia a essere effettivamente bloccato per la maggior parte del tempo, con conseguente aumento del numero di clock necessari per eseguire il blocco / sblocco. Anche il tempo medio della chiamata di funzione aumenta per questo motivo; quindi i punti della trama ora salgono e vanno di nuovo a destra.
Da ciò possiamo concludere che bloccare e sbloccare un mutex ogni 50 ns non è un problema sulla mia scatola.
Tutto sommato la mia conclusione è che la risposta alla domanda di OP è che l'aggiunta di più mutex è migliore purché si traduca in meno contese.
Prova a bloccare i mutex il più breve possibile. L'unico motivo per metterli -say- al di fuori di un loop sarebbe se quel loop si muove più rapidamente di una volta ogni 100 ns (o meglio, numero di thread che vogliono eseguire quel loop contemporaneamente per 50 ns) o quando 13 ns volte la dimensione del loop è maggiore del ritardo che si ottiene per contesa.
EDIT: Sono diventato molto più informato sull'argomento ora e inizio a dubitare della conclusione che ho presentato qui. Innanzitutto, la CPU 0 e 1 risultano essere hyper-thread; anche se AMD afferma di avere 8 core reali, c'è sicuramente qualcosa di molto sospetto perché i ritardi tra altri due core sono molto più grandi (cioè 0 e 1 formano una coppia, così come 2 e 3, 4 e 5, e 6 e 7 ). In secondo luogo, lo std :: mutex è implementato in modo da far girare i blocchi per un po 'prima di fare effettivamente chiamate di sistema quando non riesce a ottenere immediatamente il blocco su un mutex (che senza dubbio sarà estremamente lento). Quindi quello che ho misurato qui è la posizione più ideale in assoluto e in pratica il blocco e lo sblocco potrebbero richiedere molto più tempo per blocco / sblocco.
In conclusione, un mutex è implementato con l'atomica. Per sincronizzare gli atomici tra i core, è necessario bloccare un bus interno che congela la corrispondente linea di cache per diverse centinaia di cicli di clock. Nel caso in cui non sia possibile ottenere un blocco, è necessario eseguire una chiamata di sistema per mettere in pausa il thread; è ovviamente estremamente lento (le chiamate di sistema sono nell'ordine di 10 mircosecondi). Normalmente questo non è davvero un problema perché quel thread deve dormire comunque-- ma potrebbe essere un problema con alta contesa in cui un thread non può ottenere il blocco per il tempo che gira normalmente e così fa la chiamata di sistema, ma PU CAN prendere la serratura poco dopo. Ad esempio, se più thread bloccano e sbloccano un mutex in un ciclo stretto e ciascuno mantiene il blocco per 1 microsecondo circa, allora potrebbero essere rallentati enormemente dal fatto che vengono costantemente messi a dormire e svegliati di nuovo. Inoltre, una volta che un thread dorme e un altro thread deve riattivarlo, quel thread deve fare una chiamata di sistema ed è ritardato di ~ 10 microsecondi; questo ritardo si verifica quindi quando si sblocca un mutex quando un altro thread è in attesa di quel mutex nel kernel (dopo che lo spin ha richiesto troppo tempo).