Récemment, j'ai travaillé sur des projets qui utilisent fortement le filetage. Je pense que je suis OK pour les concevoir; utiliser autant que possible la conception sans état, verrouiller l'accès à toutes les ressources dont plus d'un thread a besoin, etc. Mon expérience en programmation fonctionnelle m'a énormément aidé.
Cependant, lors de la lecture du code de thread d'autres personnes, je suis confus. Je suis en train de déboguer une impasse en ce moment, et puisque le style de codage et la conception sont différents de mon style personnel, j'ai du mal à voir les conditions d'impasse potentielles.
Que recherchez-vous lors du débogage des blocages?
debugging
multithreading
Michael K
la source
la source
Réponses:
Si la situation est un véritable blocage (c'est-à-dire que deux threads détiennent deux verrous différents, mais au moins un thread veut un verrou que l'autre thread détient), vous devez d'abord abandonner toutes les préconceptions de la façon dont les threads ordonnent le verrouillage. N'assume rien. Vous voudrez peut-être supprimer tous les commentaires du code que vous regardez, car ces commentaires peuvent vous faire croire quelque chose qui ne tient pas. Il est difficile de le souligner suffisamment: ne présumez rien.
Après cela, déterminez quels verrous sont maintenus pendant qu'un thread tente de verrouiller autre chose. Si vous le pouvez, assurez-vous qu'un fil se déverrouille dans l'ordre inverse du verrouillage. Encore mieux, assurez-vous qu'un thread ne détient qu'un seul verrou à la fois.
Traitez minutieusement l'exécution d'un thread et examinez tous les événements de verrouillage. À chaque verrou, déterminez si un thread contient d'autres verrous et, dans l'affirmative, dans quelles circonstances un autre thread, effectuant un chemin d'exécution similaire, peut accéder à l'événement de verrouillage considéré.
Il est certainement possible que vous ne trouviez pas le problème avant de manquer de temps ou d'argent.
la source
Comme d'autres l'ont dit ... si vous pouvez obtenir des informations utiles pour la journalisation, essayez d'abord car c'est la chose la plus simple à faire.
Identifiez les verrous concernés. Changez tous les mutex / sémaphores qui attendent pour toujours en attentes chronométrées ... quelque chose de ridiculement long comme 5 minutes. Consignez l'erreur lorsqu'elle expire. Cela vous indiquera au moins la direction de l'un des verrous impliqués dans le problème. En fonction de la variabilité du timing, vous pourriez avoir de la chance et trouver les deux verrous après quelques exécutions. Utilisez le code / les conditions d'échec de la fonction pour enregistrer une pseudo trace de pile après que l'attente chronométrée n'a pas identifié comment vous y êtes arrivé en premier lieu. Cela devrait vous aider à identifier le thread impliqué dans le problème.
Une autre chose que vous pourriez essayer est de créer une bibliothèque de wrapper autour de vos services mutex / sémaphore. Suivez quels threads ont chaque mutex et quels threads attendent sur le mutex. Créez un thread de contrôle qui vérifie la durée de blocage des threads. Déclenchez sur une durée raisonnable et videz les informations d'état que vous suivez.
À un moment donné, une simple inspection du code ancien sera nécessaire.
la source
La première étape (comme le dit Péter) est la journalisation. Bien que, selon mon expérience, cela soit souvent problématique. Dans un traitement parallèle intensif, cela n'est souvent pas possible. Une fois, j'ai dû déboguer quelque chose de similaire avec un réseau de neurones, qui traitait 100 000 nœuds par seconde. L'erreur ne s'est produite qu'après plusieurs heures et même une seule ligne de sortie a tellement ralenti les choses que cela aurait pris des jours. Si la journalisation est possible, concentrez-vous moins sur les données, mais davantage sur le déroulement du programme, jusqu'à ce que vous sachiez dans quelle partie cela se produit. Juste une simple ligne au début de chaque fonction et si vous pouvez trouver la bonne fonction, divisez-la en petits morceaux.
Une autre option consiste à supprimer des parties du code et des données pour localiser le bogue. Peut-être même écrire un petit programme qui ne prend que certaines classes et n'exécute que les tests les plus élémentaires (toujours dans plusieurs threads bien sûr). Supprimez tout ce qui est lié à l'interface graphique, par exemple toute sortie sur l'état de traitement réel. (J'ai trouvé que l'interface utilisateur était assez souvent la source du bug)
Dans votre code, essayez de suivre le flux logique complet de contrôle entre l'initialisation du verrou et sa libération. Une erreur courante pourrait être de verrouiller au début d'une fonction, de déverrouiller à la fin, mais d'avoir une instruction de retour conditionnel quelque part entre les deux. Des exceptions pourraient également empêcher la publication.
la source
Mes meilleurs amis ont été imprimer / enregistrer des déclarations à des endroits intéressants dans le code. Ceux-ci m'aident généralement à mieux comprendre ce qui se passe réellement à l'intérieur de l'application, sans perturber le timing entre les différents threads, ce qui pourrait empêcher la reproduction du bogue.
Si cela échoue, ma seule méthode restante est de regarder le code et d'essayer de construire un modèle mental des différents threads et interactions, et d'essayer de penser à des façons folles possibles de réaliser ce qui est apparemment arrivé :-) Mais je ne le fais pas me considère comme un tueur d'impasse très expérimenté. J'espère que d'autres seront en mesure de donner de meilleures idées, dont je peux aussi apprendre :-)
la source
Tout d'abord, essayez d'obtenir l'auteur de ce code. Il aura probablement l'idée de ce qu'il avait écrit. même si vous deux ne pouvez pas localiser le problème simplement en parlant, au moins vous pouvez vous asseoir avec lui pour localiser la partie de l'impasse, ce qui sera beaucoup plus rapide que vous comprendre son code sans aide.
À défaut, comme l'a dit Péter Török, l'exploitation forestière est probablement la solution. Pour autant que je sache, le débogueur a fait un mauvais travail sur l'environnement multi-threading. essayez de localiser où se trouve la serrure, obtenez un ensemble des ressources qui attendent et dans quel état la condition de course se produit.
la source
Cette question m'attire;) Tout d'abord, considérez-vous chanceux car vous avez pu reproduire le problème de manière cohérente à chaque exécution. Si vous recevez la même exception avec la même trace de pile à chaque fois, cela devrait être assez simple. Si ce n'est pas le cas, alors ne faites pas autant confiance à la trace de pile, surveillez simplement l'accès aux objets globaux et ses changements d'état pendant l'exécution.
la source
Si vous devez déboguer des blocages, vous êtes déjà en difficulté. En règle générale, utilisez des verrous le plus rapidement possible - ou pas du tout, si possible. Toute situation où vous prenez un verrou puis passez au code non trivial doit être évitée.
Cela dépend bien sûr de votre environnement de programmation, mais vous devriez regarder des choses comme les files d'attente séquentielles qui peuvent vous permettre d'accéder à une ressource à partir d'un seul thread.
Et puis il y a une stratégie ancienne mais infaillible: attribuer un "niveau" à chaque verrou, en commençant au niveau 0. Si vous prenez un verrou de niveau 0, vous n'avez pas le droit à d'autres verrous. Après avoir pris un verrou de niveau 1, vous pouvez prendre un verrou de niveau 0. Après avoir pris un verrou de niveau 10, vous pouvez prendre des verrous de niveau 9 ou inférieur, etc.
Si vous trouvez cela impossible à faire, vous devez corriger votre code car vous rencontrerez des blocages.
la source