Je voulais savoir la même chose, alors je l'ai mesurée. Sur ma boîte (processeur AMD FX (tm) -8150 à huit cœurs à 3,612361 GHz), verrouiller et déverrouiller un mutex déverrouillé qui se trouve dans sa propre ligne de cache et est déjà mis en cache, prend 47 horloges (13 ns).
En raison de la synchronisation entre deux cœurs (j'ai utilisé les processeurs n ° 0 et n ° 1), je ne pouvais appeler une paire de verrouillage / déverrouillage qu'une fois toutes les 102 ns sur deux threads, donc une fois toutes les 51 ns, à partir de laquelle on peut conclure qu'il en faut environ 38 ns pour récupérer après qu'un thread ait fait un déverrouillage avant que le thread suivant puisse le verrouiller à nouveau.
Le programme que j'ai utilisé pour enquêter sur cela peut être trouvé ici:
https://github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39f41809d3e3de2f9e4b8/src/mutex_test.cxx
Notez qu'il a quelques valeurs codées en dur spécifiques à ma boîte (xrange, yrange et rdtsc overhead), vous devez donc probablement l'expérimenter avant que cela fonctionne pour vous.
Le graphique qu'il produit dans cet état est:
Cela montre le résultat des exécutions de référence sur le code suivant:
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;
}
Les deux appels rdtsc mesurent le nombre d'horloges qu'il faut pour verrouiller et déverrouiller le «mutex» (avec une surcharge de 39 horloges pour les appels rdtsc sur ma box). Le troisième asm est une boucle de retard. La taille de la boucle de délai est inférieure de 1 point pour le thread 1 à celle du thread 0, donc le thread 1 est légèrement plus rapide.
La fonction ci-dessus est appelée dans une boucle étroite de taille 100 000. Malgré cela, la fonction est légèrement plus rapide pour le thread 1, les deux boucles se synchronisent à cause de l'appel au mutex. Ceci est visible sur le graphique du fait que le nombre d'horloges mesurées pour la paire verrouillage / déverrouillage est légèrement plus grand pour le fil 1, pour tenir compte du délai plus court dans la boucle en dessous.
Dans le graphique ci-dessus, le point en bas à droite est une mesure avec un retard loop_count de 150, puis en suivant les points en bas, vers la gauche, le loop_count est réduit d'un à chaque mesure. Lorsqu'elle devient 77, la fonction est appelée toutes les 102 ns dans les deux threads. Si par la suite loop_count est encore plus réduit, il n'est plus possible de synchroniser les threads et le mutex commence à être réellement verrouillé la plupart du temps, ce qui entraîne une augmentation du nombre d'horloges nécessaires pour effectuer le verrouillage / déverrouillage. De plus, la durée moyenne de l'appel de fonction augmente à cause de cela; donc les points de l'intrigue montent maintenant et à nouveau vers la droite.
De cela, nous pouvons conclure que verrouiller et déverrouiller un mutex toutes les 50 ns n'est pas un problème sur ma boîte.
Dans l'ensemble, ma conclusion est que la réponse à la question de l'OP est que l'ajout de plus de mutex est préférable tant que cela entraîne moins de conflits.
Essayez de verrouiller les mutex aussi courts que possible. La seule raison de les mettre -dire- en dehors d'une boucle serait si cette boucle boucle plus vite qu'une fois toutes les 100 ns (ou plutôt, le nombre de threads qui veulent exécuter cette boucle en même temps multiplié par 50 ns) ou lorsque 13 ns fois la taille de la boucle est supérieure au délai que vous obtenez par contention.
EDIT: J'ai maintenant beaucoup plus de connaissances sur le sujet et je commence à douter de la conclusion que j'ai présentée ici. Tout d'abord, les CPU 0 et 1 s'avèrent être hyper-threadées; même si AMD prétend avoir 8 vrais cœurs, il y a certainement quelque chose de très louche car les délais entre deux autres cœurs sont beaucoup plus importants (c'est-à-dire que 0 et 1 forment une paire, tout comme 2 et 3, 4 et 5, et 6 et 7 ). Deuxièmement, le std :: mutex est implémenté de telle sorte qu'il se verrouille un peu avant de faire des appels système lorsqu'il ne parvient pas à obtenir immédiatement le verrou sur un mutex (ce qui sera sans aucun doute extrêmement lent). Donc, ce que j'ai mesuré ici est la situation la plus idéale absolue et dans la pratique, le verrouillage et le déverrouillage peuvent prendre beaucoup plus de temps par verrouillage / déverrouillage.
En bout de ligne, un mutex est implémenté avec atomics. Pour synchroniser les atomiques entre les cœurs, un bus interne doit être verrouillé, ce qui gèle la ligne de cache correspondante pendant plusieurs centaines de cycles d'horloge. Dans le cas où un verrou ne peut pas être obtenu, un appel système doit être effectué pour mettre le thread en veille; c'est évidemment extrêmement lent (les appels système sont de l'ordre de 10 mircosecondes). Normalement, ce n'est pas vraiment un problème car ce thread doit de toute façon dormir - mais cela pourrait être un problème avec une forte contention où un thread ne peut pas obtenir le verrou pendant le temps qu'il tourne normalement et l'appel système aussi, mais CAN prenez la serrure peu de temps après. Par exemple, si plusieurs threads verrouillent et déverrouillent un mutex dans une boucle serrée et que chacun garde le verrou pendant environ 1 microseconde, alors ils pourraient être considérablement ralentis par le fait qu'ils sont constamment endormis et réveillés à nouveau. De plus, une fois qu'un thread est en veille et qu'un autre thread doit le réveiller, ce thread doit faire un appel système et est retardé d'environ 10 microsecondes; ce délai se produit donc lors du déverrouillage d'un mutex lorsqu'un autre thread attend ce mutex dans le noyau (après que la rotation ait pris trop de temps).