Qu'est-ce que la contention de thread?

121

Quelqu'un peut-il expliquer simplement ce qu'est la contention de thread?

Je l'ai googlé, mais je n'arrive pas à trouver une explication simple.

Tony le lion
la source
10
Alors, écrivez ce que vous pensez, afin que nous puissions voir où vous en êtes, ou votre compréhension peut être correcte.
James Black

Réponses:

89

Le conflit de threads est essentiellement une condition dans laquelle un thread attend un verrou / objet actuellement détenu par un autre thread. Par conséquent, ce thread en attente ne peut pas utiliser cet objet tant que l'autre thread n'a pas déverrouillé cet objet particulier.

clavierP
la source
53
Cette réponse est incomplète (comme la plupart des autres). Bien qu'une serrure soit un type de chose sur laquelle il peut y avoir des conflits, elle est loin d'être la seule. Il peut également y avoir des conflits pour les ressources sans verrouillage. (Par exemple, si deux threads continuent d'incrémenter atomiquement le même entier, ils peuvent rencontrer des conflits en raison du ping-pong du cache. Aucun verrou n'est impliqué.)
David Schwartz
Dans le cas d'un Global Interpreter Lock (GIL) comme en CPython, où un thread doit toujours acquérir le GIL, plusieurs threads s'exécutant dans le même processus sont donc en conflit par défaut.
Acumenus
Je pense que vous l'avez expliqué en termes de Deadlock, mais c'est très différent de Deadlock.
Harshit Gupta
185

Plusieurs réponses semblent se concentrer sur les conflits de verrouillage, mais les verrous ne sont pas les seules ressources sur lesquelles des conflits peuvent être rencontrés. Le conflit se produit simplement lorsque deux threads tentent d'accéder à la même ressource ou aux ressources associées de telle sorte qu'au moins l'un des threads en concurrence s'exécute plus lentement que si les autres threads n'étaient pas en cours d'exécution.

L'exemple le plus évident de conflit est celui d'un verrou. Si le thread A a un verrou et que le thread B veut acquérir ce même verrou, le thread B devra attendre que le thread A libère le verrou.

Maintenant, c'est spécifique à la plate-forme, mais le thread peut rencontrer des ralentissements même s'il n'a jamais à attendre que l'autre thread libère le verrou! En effet, un verrou protège certains types de données, et les données elles-mêmes seront souvent également contestées.

Par exemple, considérons un thread qui acquiert un verrou, modifie un objet, puis libère le verrou et effectue d'autres opérations. Si deux threads font cela, même s'ils ne se battent jamais pour le verrou, les threads peuvent s'exécuter beaucoup plus lentement qu'ils ne le feraient si un seul thread était en cours d'exécution.

Pourquoi? Supposons que chaque thread s'exécute sur son propre cœur sur un processeur x86 moderne et que les cœurs ne partagent pas un cache L2. Avec un seul thread, l'objet peut rester dans le cache L2 la plupart du temps. Avec les deux threads en cours d'exécution, chaque fois qu'un thread modifie l'objet, l'autre thread trouvera que les données ne sont pas dans son cache L2 car l'autre CPU a invalidé la ligne de cache. Sur un Pentium D, par exemple, cela entraînera l'exécution du code à la vitesse FSB, qui est bien inférieure à la vitesse du cache L2.

Étant donné qu'un conflit peut se produire même si le verrou n'est pas lui-même contesté, un conflit peut également se produire lorsqu'il n'y a pas de verrou. Par exemple, supposons que votre CPU prend en charge un incrément atomique d'une variable 32 bits. Si un thread continue d'incrémenter et de décrémenter une variable, la variable sera chaude dans le cache la plupart du temps. Si deux threads le font, leurs caches se disputeront la propriété de la mémoire contenant cette variable, et de nombreux accès seront plus lents car le protocole de cohérence du cache fonctionne pour sécuriser la propriété de chaque noyau de la ligne de cache.

Ironiquement, les verrous réduisent généralement les conflits. Pourquoi? Parce que sans verrou, deux threads pourraient fonctionner sur le même objet ou collection et provoquer de nombreux conflits (par exemple, il existe des files d'attente sans verrou). Les verrous auront tendance à désordonner les threads rivaux, permettant aux threads non concurrents de s'exécuter à la place. Si le thread A détient un verrou et que le thread B veut ce même verrou, l'implémentation peut exécuter le thread C à la place. Si le thread C n'a pas besoin de ce verrou, alors les conflits futurs entre les threads A et B peuvent être évités pendant un certain temps. (Bien sûr, cela suppose qu'il existe d'autres threads qui pourraient s'exécuter. Cela n'aidera pas si le seul moyen pour le système dans son ensemble de faire des progrès utiles est d'exécuter des threads qui se disputent.)

David Schwartz
la source
4
+1 Aussi, juste pour rendre cela explicite, les deux variables sur lesquelles deux cœurs se disputent n'ont même pas besoin d'être la même variable pour provoquer des conflits, elles doivent seulement être stockées en mémoire dans la même ligne de cache. Le remplissage des structures et / ou l'alignement des structures sur la mémoire peuvent aider à éviter cette forme de conflit.
Rob_before_edits
1
@David s'il vous plaît aider à comprendre le dernier paragraphe de votre réponse plus en détail
Apprenant
4
@Naroji Posez une question à ce sujet.
David Schwartz
@DavidSchwartz, êtes-vous un programmeur C?
Pacerier
@Pacerier C ++ principalement.
David Schwartz
19

D' ici :

Un conflit se produit lorsqu'un thread attend une ressource qui n'est pas facilement disponible; cela ralentit l'exécution de votre code, mais peut s'éclaircir avec le temps.

Un blocage se produit lorsqu'un thread attend une ressource verrouillée par un second thread et que le second thread attend une ressource que le premier thread a verrouillée. Plus de deux threads peuvent être impliqués dans un blocage. Une impasse ne se résout jamais. Cela provoque souvent l'arrêt de l'ensemble de l'application ou de la partie qui rencontre le blocage.

Jon B
la source
Cela explique également la différence entre thread Contention et Deadlock
Sankalp
3

Je pense que le PO devrait clarifier le contexte de la question - je peux penser à 2 réponses (même si je suis sûr qu'il y a des ajouts à cette liste):

  1. si vous faites référence au «concept» général de conflit de threads et comment il peut se présenter dans une application, je m'en remets à la réponse détaillée de @ DavidSchwartz ci-dessus.

  2. Il existe également le compteur de performances «.NET CLR Locks and Threads: Total # of Contentions». Comme extrait de la description PerfMon de ce compteur, il est défini comme suit:

    Ce compteur affiche le nombre total de fois que les threads du CLR ont tenté d'acquérir un verrou géré sans succès. Les serrures gérées peuvent être acquises de plusieurs manières; par l'instruction «lock» en C # ou en appelant System.Monitor.Enter ou en utilisant l'attribut personnalisé MethodImplOptions.Synchronized.

... et je suis sûr que d'autres pour d'autres OS et frameworks d'application.

Dave Black
la source
2

Vous avez 2 fils. Thread A et Thread B, vous avez également l'objet C.

A accède actuellement à l'objet C et a placé un verrou sur cet objet. B doit accéder à l'objet C, mais ne peut pas le faire tant que A n'a pas relâché le verrou sur l'objet C.

Aaron M
la source
1

Un autre mot pourrait être la concurrence. C'est simplement l'idée que deux threads ou plus essaient d'utiliser la même ressource.

Mark Wilkins
la source
1

Pour moi, la contention est une compétition entre 2 ou plusieurs threads sur une ressource partagée. La ressource peut être un verrou, un compteur, etc. La compétition signifie «qui l'obtient en premier». Plus il y a de fils, plus il y a de conflit. Plus l'accès à une ressource est fréquent, plus il y a de conflit.

Maciej
la source
1

Imaginez le scénario suivant. Vous vous préparez pour l'examen final de demain et vous avez un peu faim. Alors, vous donnez dix dollars à votre jeune frère et lui demandez de vous acheter une pizza. Dans ce cas, vous êtes le thread principal et votre frère est un thread enfant. Une fois votre commande passée, vous et votre frère faites leur travail simultanément (c.-à-d. Étudier et acheter une pizza). Maintenant, nous avons deux cas à considérer. Tout d'abord, votre frère ramène votre pizza et termine pendant que vous étudiez. Dans ce cas, vous pouvez arrêter d'étudier et profiter de la pizza. Deuxièmement, vous terminez votre étude tôt et vous dormez (c'est-à-dire que le travail qui vous est assigné pour aujourd'hui - étudier pour l'examen final de demain - est terminé) avant que la pizza ne soit disponible. Bien sûr, vous ne pouvez pas dormir; sinon, vous n'aurez pas la chance de manger la pizza.

Comme dans l'exemple, les deux cas donnent un sens à la rivalité.

snr
la source
0

Les conflits de threads sont également affectés par les opérations d'E / S. Exemple lorsqu'un thread en attente de lecture de fichier peut être considéré comme un conflit. Utilisez les ports d'achèvement d'E / S comme solution.

amilamad
la source
0

Un conflit de verrouillage a lieu lorsqu'un thread tente d'acquérir le verrou d'un objet qui est déjà acquis par un autre thread *. Tant que l'objet n'est pas libéré, le thread est bloqué (en d'autres termes, il est en attente). Dans certains cas, cela peut conduire à une soi-disant exécution en série qui affecte négativement l'application.

à partir de la documentation dotTrace

AndreyT
la source