J'avais une interview à peine 5 minutes en arrière, je n'ai pas répondu à 3 questions, quelqu'un pourrait-il m'aider s'il vous plaît.
Question:
Comment rechercher des scénarios de blocage dans une fonction d'application multithread et les empêcher?
Réponse que j'ai donnée:
J'ai donné la définition de l'impasse et verrouiller, mutex, moniteur, sémaphore. Il m'a dit que ce sont des outils, mais comment chercher un scénario de blocage car, lorsque nous utilisons ces outils à l'aveuglette, cela coûte la performance qu'il a dite :(
S'il vous plaît aidez-moi à comprendre cela.
Les outils d'analyse des performances peuvent également être utiles pour identifier les blocages, entre autres. Cette question fournira des informations sur ce sujet: C #/Outil d’analyse .NET pour la recherche de conditions de concurrence/impasses .
L'analyse visuelle du code et l'utilisation correcte des verrous sont également utiles (vous devriez être capable de détecter des problèmes potentiels dans le code lors de son inspection), mais cela peut être très difficile pour des applications complexes. Parfois, les blocages ne sont visibles que lorsque vous exécutez le code, pas simplement en inspectant le code.
Je ne connais pas trop votre intervieweur. Certains voudront peut-être savoir à quel point vous connaissez les normes de verrouillage/directives, d'autres voudront savoir si vous savez utiliser vos outils, d'autres voudront peut-être les deux. Dans l'entreprise où je travaille, par exemple, l'utilisation d'outils (en particulier ceux que nous possédons et utilisons déjà) est très appréciée. Mais cela ne veut pas dire qu’il ne faut pas posséder les compétences nécessaires pour éviter le blocage des impasses.
Verrouiller quelque chose juste pour verrouiller affecte les performances, car les threads s’attendent. Vous devez analyser le flux de travail pour déterminer ce qui doit vraiment être verrouillé, quand, avec quel type de verrou (simple lock
ou peut-être un ReaderWriterLockSlim
). Il existe de nombreux moyens de prévenir l’impasse.
Par exemple, lorsque vous utilisez ReaderWriterLockSlim
, vous pouvez utiliser un délai d’attente pour éviter les blocages (si vous attendez trop, vous obtiendrez le verrou).
if (cacheLock.TryEnterWriteLock(timeout))
{
...
}
Et vous devriez pouvoir suggérer de tels délais.
À une telle question, je m'attendrais au moins à une mention du cas classique des impasses, comme le mauvais usage des verrous imbriqués (vous devriez pouvoir savoir si vous pouvez les éviter, pourquoi ils sont mauvais, etc.).
Le sujet est très vaste ... vous pouvez continuer encore et encore. Mais sans definitios. Savoir ce qu'est un verrou et savoir utiliser des verrous/sémaphores/mutex dans des applications à plusieurs gammes à grande échelle sont deux choses différentes.
Il semble que vous ayez eu du mal à expliquer comment les blocages peuvent se produire et comment les prévenir.
Un blocage se produit lorsque chacun des deux (au moins deux) threads essaie d'acquérir un verrou sur une ressource déjà verrouillée par un autre. Le thread 1 verrouillé sur les ressources 1 tente d'acquérir un verrou sur la ressource 2. En même temps, le thread 2 verrouille la ressource 2 et essaie d'acquérir le verrou sur la ressource 1. Deux threads n'abandonnent jamais leurs verrous, ce qui provoque un DADLOCK. .
Le moyen le plus simple d'éviter un blocage est d'utiliser une valeur de délai d'attente. La classe Monitor (system.Threading.Monitor) peut définir un délai d’expiration lors de l’acquisition d’un verrou.
Exemple
try{
if(Monitor.TryEnter(this, 500))
{
// critical section
}
}
catch (Exception ex)
{
}
finally
{
Monitor.Exit();
}
Je pense que l'entrevue vous pose une question piège. Si vous pouviez utiliser l'analyse statique pour éviter une impasse ... personne ne l'aurait!
Personnellement, lorsque je cherche une impasse, je commence par rechercher des fonctions pour lesquelles la section critique couvre plus que l'appel de fonction. Par exemple
void func(){
lock(_lock){
func2();
}
}
Ce que func2
fait n'est pas vraiment clair. Peut-être qu'il distribue un événement sur le même thread, ce qui voudrait dire que cet événement fait toujours partie de la section critique. Peut-être qu'il se verrouille alors sur un différent lock. Peut-être qu'il envoie dans le threadpool et n'est plus ré-entrant car c'est maintenant sur un autre thread! Vous pouvez commencer à voir des situations d’interblocage dans ces types d’endroits: lorsque vous avez plusieurs emplacements de verrouillage non réentrants.
D'autres fois, lors du traçage des scénarios de blocage, je retourne en arrière et j'essaie de trouver où sont créés tous les threads. Je réfléchis à chaque fonction et aux endroits où elle peut fonctionner. Si vous n’êtes pas sûr, il peut également être utile d’ajouter une consignation pour consigner l’origine de l’appel de fonction.
Vous pouvez également éviter les blocages en utilisant des structures de données sans verrouillage (mais celles-ci nécessitent tout autant leur utilisation). Vous souhaitez minimiser votre accès à la structure sans verrou, car chaque fois que vous y accédez, il peut changer.
Comme mentionné dans une autre réponse, vous pouvez utiliser des mutex avec des délais d'expiration, mais cela ne garantit pas toujours de fonctionner (que se passe-t-il si votre code doit fonctionner plus longtemps que le délai d'expiration?). Il a été mentionné dans un autre commentaire que c’était peut-être ce que l’intervieweur demandait. Je trouve qu'en production ce n'est pas vraiment une bonne idée. Les délais d'attente varient constamment, peut-être que quelque chose a pris plus de temps que prévu pour s'exécuter et atteindre un délai d'attente. Je pense qu'il est préférable de laisser l'impasse, de prendre un dump de processus, puis de trouver exactement ce qui tenait les verrous et de résoudre le problème. Bien entendu, si les exigences de votre entreprise ne le permettent pas, vous pouvez l’utiliser dans le cadre d’une stratégie de codage défensif et d’options d’emplacement des verrous intelligents.
Je ne suis pas d'accord avec votre entrevue qui verrouille toujours ajoute un énorme problème de performance. Les verrous/mutexes/etc non contrôlés doivent d’abord être testés comme spinlock avant de passer au système d’exploitation, et les spinlocks sont bon marché.
En général, le meilleur moyen d'éviter une impasse est de comprendre le déroulement de votre programme. Chaque fois que vous introduisez un nouvel objet de verrouillage, pensez à son utilisation et à son utilisation dans la chaîne.
La solution la plus simple à ce problème serait de toujours dormir/attendre avec un délai d’attente suffisamment long. Si ce délai arrive, vous savez que quelque chose a pris beaucoup plus de temps que prévu et vous avez de bonnes chances d'être bloqué ou d'avoir un autre bug.
Mutex m; // similar overloads exist for all locking primitives
if (!m.WaitOne(TimeSpan.FromSeconds(30)))
throw new Exception("Potential deadlock detected.");
Lorsque WaitOne
retourne false
, il aura attendu 30 secondes et le verrou n'aurait toujours pas été libéré. Si vous savez que toutes les opérations verrouillées doivent être terminées en quelques millisecondes (sinon, augmentez simplement le délai), c'est une très bonne indication que quelque chose s'est mal passé.