Question Qu'est-ce qu'une condition de course?


Lors de l'écriture d'applications multithread, l'un des problèmes les plus courants rencontrés sont les conditions de course.

Mes questions à la communauté sont:

Qu'est-ce qu'une condition de course? Comment les détectez-vous? Comment les gérez-vous? Enfin, comment les empêchez-vous de se produire?


736
2017-08-29 15:55


origine


Réponses:


Une condition de concurrence se produit lorsque deux threads ou plus peuvent accéder aux données partagées et qu'ils essaient de les modifier en même temps. Étant donné que l'algorithme de planification des threads peut permuter entre les threads à tout moment, vous ne connaissez pas l'ordre dans lequel les threads tenteront d'accéder aux données partagées. Par conséquent, le résultat de la modification des données dépend de l'algorithme d'ordonnancement des threads, c'est-à-dire que les deux threads "courent" pour accéder / modifier les données.

Des problèmes surviennent souvent quand un thread fait un "check-then-act" (par exemple "check" si la valeur est X, alors "act" fait quelque chose qui dépend de la valeur étant X) et un autre thread fait quelque chose à la valeur entre le "contrôle" et le "acte". Par exemple:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

Le point étant, y pourrait être 10, ou cela pourrait être n'importe quoi, selon qu'un autre thread a changé x entre la vérification et l'action. Vous n'avez pas de véritable moyen de savoir.

Afin d'éviter les conditions de concurrence, vous devez généralement verrouiller les données partagées pour vous assurer qu'un seul thread peut accéder aux données à la fois. Cela voudrait dire quelque chose comme ceci:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x

917
2017-08-29 16:05



Une "condition de concurrence" existe lorsque le code multithread (ou parallèlement) qui accède à une ressource partagée peut le faire de manière à provoquer des résultats inattendus.

Prenons cet exemple:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

Si vous aviez 5 threads exécutant ce code à la fois, la valeur de x ne DEVRAIT PAS être 50 000 000. Cela varierait en fait avec chaque course.

C'est parce que, pour que chaque thread incrémente la valeur de x, ils doivent faire ce qui suit: (simplifié, évidemment)

Récupérer la valeur de x
Ajouter 1 à cette valeur
Stockez cette valeur à x

N'importe quel thread peut être à n'importe quelle étape de ce processus à tout moment, et ils peuvent s'interpénétrer lorsqu'une ressource partagée est impliquée. L'état de x peut être changé par un autre thread pendant le temps entre la lecture de x et sa réécriture.

Supposons qu'un thread récupère la valeur de x, mais ne l'ait pas encore stocké. Un autre thread peut également récupérer le même valeur de x (parce qu'aucun fil n'a encore changé), puis ils seraient tous les deux stocker la même valeur (x + 1) de retour dans x!

Exemple:

Fil 1: lit x, la valeur est 7
Thread 1: ajouter 1 à x, la valeur est maintenant 8
Fil 2: lit x, la valeur est 7
Thread 1: stocke 8 dans x
Thread 2: ajoute 1 à x, la valeur est maintenant 8
Fil 2: magasins 8 en x

Les conditions de course peuvent être évitées en employant une sorte de verrouillage mécanisme avant le code qui accède à la ressource partagée:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

Ici, la réponse est de 50 000 000 à chaque fois.

Pour en savoir plus sur le verrouillage, recherchez: mutex, sémaphore, section critique, ressource partagée.


176
2017-08-29 17:01



Qu'est-ce qu'un état de la course?

Vous prévoyez d'aller au cinéma à 17 heures. Vous vous renseignez sur la disponibilité des billets à 16h. Le représentant dit qu'ils sont disponibles. Vous vous relaxez et atteignez la billetterie 5 minutes avant le spectacle. Je suis sûr que vous pouvez deviner ce qui se passe: c'est une maison pleine. Le problème ici était dans la durée entre le contrôle et l'action. Vous avez demandé à 4 et agi à 5. En attendant, quelqu'un d'autre a attrapé les billets. C'est une condition de course - en particulier un scénario de «contrôle-puis-acte» des conditions de course.

Comment les détectez-vous?

Revue de code religieux, tests unitaires multithread. Il n'y a pas de raccourci. Il y a peu de plugin Eclipse sur ce sujet, mais rien de stable pour le moment.

Comment gérez-vous et empêchez-les?

La meilleure chose serait de créer des fonctions libres et sans état, utilisez immuables autant que possible. Mais ce n'est pas toujours possible. Ainsi, l'utilisation de java.util.concurrent.atomic, de structures de données concurrentes, d'une synchronisation correcte et d'une simultanéité basée sur l'acteur aidera.

La meilleure ressource pour la concurrence est JCIP. Vous pouvez aussi en avoir plus détails sur l'explication ci-dessus ici.


111
2017-10-04 21:20



Il existe une différence technique importante entre les conditions de course et les courses de données. La plupart des réponses semblent faire l'hypothèse que ces termes sont équivalents, mais ils ne le sont pas.

Une course de données se produit lorsque 2 instructions accèdent au même emplacement de mémoire, au moins un de ces accès est une écriture et il n'y a pas se passe avant de commander parmi ces accès. Maintenant, ce qui constitue un événement avant de commander est sujet à beaucoup de discussions, mais en général, les paires ulock-lock sur la même variable lock et les paires wait-signal sur la même variable de condition induisent un ordre passe-avant.

Une condition de concurrence est une erreur sémantique. C'est un défaut qui se produit dans le calendrier ou l'ordre des événements qui mène à un programme erroné comportement.

De nombreuses conditions de course peuvent être (et sont en fait) causées par des courses de données, mais cela n'est pas nécessaire. En fait, les courses de données et les conditions de course ne sont ni la condition nécessaire, ni la condition suffisante l'une pour l'autre. Ce blog post explique également très bien la différence, avec un simple exemple de transaction bancaire. Voici un autre simple Exemple cela explique la différence.

Maintenant que nous avons cloué la terminologie, essayons de répondre à la question initiale.

Étant donné que les conditions de course sont des bogues sémantiques, il n'y a pas de moyen général de les détecter. C'est parce qu'il n'y a aucun moyen d'avoir un oracle automatisé qui peut distinguer le comportement de programme correct contre incorrect dans le cas général. La détection de course est un problème indécidable.

D'autre part, les courses de données ont une définition précise qui ne se rapporte pas nécessairement à la correction, et donc on peut les détecter. Il existe de nombreuses variantes de détecteurs de données (détection de course de données statique / dynamique, détection de course de données basée sur un verrou, détection de course de données basée sur le passé, détection de course de données hybride). Un détecteur de course de données dynamique à la pointe de la technologie est ThreadSanitizer ce qui fonctionne très bien dans la pratique.

La gestion des courses de données en général nécessite une certaine discipline de programmation pour induire des fronts arrivant avant entre les accès aux données partagées (soit pendant le développement, soit une fois qu'ils sont détectés en utilisant les outils mentionnés ci-dessus). ceci peut être fait à travers des verrous, des variables de condition, des sémaphores, etc. Cependant, on peut aussi utiliser différents paradigmes de programmation comme le passage de message (au lieu de mémoire partagée) qui évite les courses de données par construction.


52
2017-08-29 08:45



Une définition de type canonique est "lorsque deux threads accèdent au même emplacement en mémoire en même temps, et au moins l'un des accès est un write"Dans la situation, le thread" lecteur "peut obtenir l'ancienne valeur ou la nouvelle valeur, selon le thread qui" gagne la course ". Ce n'est pas toujours un bug - en fait, certains algorithmes de bas niveau très velus le font but - mais il devrait généralement être évité. @Steve Gury donner un bon exemple de quand il pourrait être un problème.


32
2017-08-29 16:21



Une condition de race est une sorte de bug, qui se produit uniquement avec certaines conditions temporelles.

Exemple: Imaginez que vous ayez deux fils, A et B.

Dans le fil A

if( object.a != 0 )
    object.avg = total / object.a

Dans le fil B:

object.a = 0

Si le thread A est préempté juste après avoir vérifié que object.a n'est pas nul, B fera a = 0, et quand le thread A gagnera le processeur, il fera un "diviser par zéro".

Ce bug survient uniquement lorsque le thread A est préempté juste après l'instruction if, c'est très rare, mais cela peut arriver.


27
2017-08-29 16:03



Les conditions de course se produisent dans des applications multithreads ou des systèmes multiprocessus. Une condition de concurrence, à la base, est tout ce qui fait l'hypothèse que deux choses qui ne sont pas dans le même fil ou processus se produiront dans un ordre particulier, sans prendre de mesures pour s'assurer qu'elles le font. Cela se produit généralement lorsque deux threads transmettent des messages en définissant et en vérifiant les variables membres d'une classe à laquelle les deux peuvent accéder. Il y a presque toujours une condition de concurrence lorsqu'un thread appelle sleep pour donner un autre thread pour finir une tâche (à moins que ce sleep ne soit dans une boucle, avec un mécanisme de vérification).

Les outils de prévention des conditions de course dépendent du langage et du système d'exploitation, mais certains d'entre eux sont des mutex, des sections critiques et des signaux. Les mutex sont bons quand vous voulez vous assurer que vous êtes le seul à faire quelque chose. Les signaux sont bons quand vous voulez vous assurer que quelqu'un d'autre a fini de faire quelque chose. Minimiser les ressources partagées peut également aider à prévenir les comportements inattendus

La détection des conditions de course peut être difficile, mais il y a quelques signes. Le code qui repose fortement sur le sommeil est sujet aux conditions de course, alors vérifiez d'abord que les appels se trouvent dans le code affecté. L'ajout de sleepps particulièrement longs peut également être utilisé pour le débogage afin d'essayer de forcer un ordre particulier d'événements. Cela peut être utile pour reproduire le comportement, voir si vous pouvez le faire disparaître en changeant le timing des choses, et pour tester les solutions mises en place. Les sommeils doivent être supprimés après le débogage.

Le signe de signature que l'on a une condition de concurrence est, cependant, s'il y a un problème qui se produit seulement par intermittence sur certaines machines. Les bogues communs seraient des accidents et des blocages. Avec la journalisation, vous devriez être en mesure de trouver la zone affectée et de travailler à partir de là.


18
2017-08-29 16:12