9
votes

Threads et Cure de verrouillage simple simple

Lorsque vous traitez avec des threads (spécifiquement en C ++) à l'aide de verrous Mutex et de SEMAPHORES est une simple règle de pouce pour éviter les verrous morts et avoir une belle synchronisation propre?


0 commentaires

9 Réponses :


0
votes

lire Deadlock: Le problème et un Solution.

"Le conseil commun pour éviter l'impasse est de toujours verrouiller les deux mutiles dans le même ordre: si vous verrouillez toujours mutex A avant le mutex B, alors vous n'allez jamais une impasse. Parfois, cela est simple, car les mutiles servent différemment. Mais d'autres fois, il n'est pas aussi simple, comme lorsque les mutiles protègent chacun une instance séparée de la même classe ".


0 commentaires

16
votes

Une bonne règle simple est de toujours obtenir vos serrures dans un ordre prévisible constant de partout dans votre application. Par exemple, si vos ressources ont des noms, même les enfermer par ordre alphabétique. S'ils ont des identifiants numériques, serront toujours du plus bas au plus élevé. L'ordre ou les critères exacts est arbitraire. La clé est d'être cohérente. De cette façon, vous n'aurez jamais une situation d'impasse. p ex.

  1. fil 1 verrouille la ressource A
  2. fil 2 verrouillons la ressource B
  3. Le fil 1 attend d'obtenir un verrou sur B
  4. Le fil 2 attend d'obtenir un verrou sur un
  5. Deadlock

    Ce qui précède ne peut jamais arriver si vous suivez la règle de base décrite ci-dessus. Pour une discussion plus détaillée, voir le Entrée Wikipedia sur le problème des philosophes de restauration .


0 commentaires

2
votes

Essayez d'éviter d'acquérir une serrure et d'essayer d'en acquérir une autre. Cela peut entraîner une dépendance circulaire et une motion pour une impasse. S'il est désagréable, alors au moins l'ordre d'acquérir des serrures doit être prévisible.

Utilisez RAII (pour vous assurer que le verrouillage est correctement en cas d'exception également)


6 commentaires

Cela ne sonne pas pratique. Il y a beaucoup de situations dans lesquelles vous devez conserver plus d'une serrure à la fois. Ceci est particulièrement vrai si vous utilisez des serrures granulaires au lieu d'un global.


Une façon de rendre cela plus pratique consiste à utiliser une API qui vous permet d'acquérir un ensemble de serrures en même temps. Si vous savez que vous avez besoin de verrous A, B et C, Call Verrouiller (A, B, C) et la fonction tentera de les verrouiller toutes et de le faire dans le bon ordre. Vous pouvez écrire vous-même une telle fonction pour centraliser le code permettant d'acquérir des serrures dans le bon ordre.


Pourquoi n'est-ce pas pratique? Il est plus sûr de libérer l'appel mutex avant d'entrer dans un autre composant (là en essayant d'acquérir un autre verrou). "Hold and Wait" est celui de la raison de base de l'impasse


AJ: Beaucoup de problèmes nécessitent que vous achetiez plusieurs serrures. Il n'est pas pratique de ne jamais tenir une seule serrure à la fois.


C'est difficile mais est-il vraiment impraticable de concevoir comme ça?


Il n'est pas pratique de concevoir comme ça et être toujours performant. Bien qu'il puisse y avoir des moyens qui m'échappent, la façon habituelle de faire plus avec moins de serrures est de rendre les serrures plus larges. C'est bien jusqu'à ce que vous ayez beaucoup de threads, puis ils bloquent trop souvent et les réservoirs de performance. Les serrures globales sont une solution pour les blocages, mais Thye est la mort à la performance.



7
votes
  1. Si possible, concevez votre code pour que vous n'ayez jamais à verrouiller plus, puis à un seul mutex / sémaphore à la fois.
  2. Si cela n'est pas possible, assurez-vous de toujours verrouiller plusieurs mutex / sémaphores dans le même ordre. Donc, si une partie du code verrouille mutex a puis prend sémaphore B, assurez-vous qu'aucune autre partie du code ne prend sémaphore B puis verrouille Mutex A.

1 commentaires

+1 exactement la réponse que j'allais donner. Meilleur avis de programmation simultané que j'ai jamais entendu.



1
votes

Il n'y a pas de cure d'impasse simple.

Acquérir des serrures dans l'ordre convenu: Si tous les appels acquièrent A-> B-> C, aucune impasse ne peut se produire. Les blocages ne peuvent se produire que si l'ordre de verrouillage diffère entre les deux threads (on acquiert A-> B le second B-> A).

Dans la pratique est difficile de choisir une commande entre objets arbitraires en mémoire. Sur un simple projet trivial est possible, mais sur de grands projets avec de nombreux contributeurs individuels est très difficile. Une solution partielle consiste à créer des hiérarchies, en classant les serrures. Tous les verrous dans le module A ont un rang 1, tous les verrous dans le module B ont un rang 2. On peut acquérir une serrure de rang 2 lors de la serrure du rang 1, mais pas vice-versa. Bien sûr, vous avez besoin d'un cadre autour des primitives de verrouillage qui suivent et valide le classement.


2 commentaires

En C ++, choisir une commande est très simple. Juste commander par adresse mémoire. En C # ou Java, il est plus difficile que l'adresse d'un objet n'est pas exposée au programmeur et peut changer pendant la durée de vie de l'objet.


J'aimerais que les choses soient si faciles ... Si vous êtes dans la position rare que le code dispose d'une liste d'objets pour verrouiller A, B, C Oui, vous pouvez comparer les adresses. Mais la plupart des temps, A est verrouillé dans une fonction F (), B est verrouillé en G () et C est verrouillé DIN H () et aucune de ces fonctions ne connaissent l'autre. Il est un peu irréaliste d'assumer dans une application que toutes les serrures seront acquises en blocs, sachant que toutes les serrures seront et une référence à chacun.



0
votes

Un moyen d'assurer la commande que d'autres personnes ont parlé est d'acquérir des serrures dans une commande définie par leur adresse mémoire. Si, à tout moment, vous essayez d'acquérir un verrou qui aurait dû être plus tôt dans la séquence, vous libérez tous les serrures et recommencez.

Avec un peu de travail, il est possible de le faire presque automatiquement avec des classes d'emballage autour des primitives système.


0 commentaires

0
votes

Il n'y a pas Cure pratique . Plus précisément, il n'ya aucun moyen de simplement tester le code pour être correctement correct de synchronisation ou pour que vos programmeurs obéissent aux règles du gentleman avec le Vert v.

Il n'y a aucun moyen de tester correctement le code multithreadé, car la logique du programme peut dépendre de la synchronisation de l'acquisition de verrous et donc d'être différente de l'exécution à l'exécution, d'une manière d'une manière ou d'une autre invalidation du concept de QA.

Je dirais

  • préférez utiliser des threads uniquement comme une optimisation des performances pour les machines multicœurs
  • optimise uniquement les performances lorsque vous êtes sûr de votre performance
  • Vous pouvez utiliser des threads pour simplifier la logique du programme, mais uniquement lorsque vous êtes absolument sûr de ce que vous faites. Soyez très prudent et que tous les serrures sont confinés à un très petit morceau de code. Ne laissez aucun débutant près de ce code.
  • N'utilisez jamais de threads dans un système critique de la mission, tels que voler un avion ou utiliser des machines dangereuses
  • Dans tous les cas, les threads sont rarement rentables, en raison des coûts plus élevés de débogage et d'assurance qualité

    Si vous avez décidé de faire des threads ou de maintenir la base de code existante:

    • Confinez tous les serrures à de petites et simples pièces de code, qui fonctionnent sur les primitives
    • Évitez les appels de fonction ou l'élimination du programme s'éloigner de l'endroit où le fait d'être exécuté sous serrure n'est pas immédiatement visible. Cette fonction changera par des auteurs futurs, élargissant votre span de verrouillage sans votre contrôle.
    • Obtenez des verrous à l'intérieur d'objets pour réduire la portée de verrouillage, envelopper des objets 3RD-Parties non sécurisés sans fil avec vos propres interfaces à fil-sécurité.
    • Ne jamais envoyer de notifications synchrones (rappels) lors de l'exécution sous LOCK
    • n'utilisez que des serrures RAII, afin de réduire la charge cognitive lors de la pensée "Que pensons-nous de savoir d'ici", comme dans les exceptions, etc.

      Quelques mots sur la façon d'éviter la multi-threading.

      Une conception à une seule-filetée implique généralement une fonction de cœur de cœur fournie par des composants du programme et appelé dans une boucle (appelée cycle de battement de cœur) qui, lorsqu'il est appelé, donne une chance à tous les composants de faire le prochain morceau de travail et de redonnez le contrôle de nouveau. Quels algorithmistes aiment penser comme des "boucles" à l'intérieur des composants, se transformeront en machines à l'état pour identifier quelle est la prochaine chose à faire lorsqu'elle est appelée. L'état est mieux maintenu en tant que données de membres d'objets respectifs.


1 commentaires

En essayant de boller la multithreading sur une application après il a été écrit est une recette de défaillance. Si vous allez utiliser des threads, faites-le correctement et pensez-le dans l'architecture de l'application à partir de la journée 1. Sinon, vous obtiendrez toutes les mauvaises impasses et conditions de course. Et soyons honnêtes, à moins que vous viviez toujours dans les années 80, toute application qui prétend être même un peu importante de la CPU (ou doit être suffisante) ne peut pas vraiment éviter le filetage.



0
votes

Il y a beaucoup de simples "remorques caresses". Mais aucun qui est facile à appliquer et à travailler universellement.

Le plus simple de tous, bien sûr, est "Jamais plus d'un fil".

En supposant que vous avez une application multithreadée cependant, il existe encore un certain nombre de solutions:

Vous pouvez essayer de minimiser l'état et la synchronisation partagés. Deux threads qui viennent de courir en parallèle et n'interagissent jamais ne peuvent jamais être une impasse. Les blocages ne se produisent que lorsque plusieurs threads tentent d'accéder à la même ressource. Pourquoi font-ils ça? Cela peut-il être évité? La ressource peut-elle être restructurée ou divisée de sorte que, par exemple, un thread peut y écrire et d'autres threads sont asynchroneusement les données dont elles ont besoin?

Peut-être que la ressource peut être copiée, donnant à chaque fil de sa propre copie privée pour travailler?

Et comme déjà mentionné par toutes les autres réponses, si et lorsque vous essayez d'acquérir des serrures, faites-le dans un ordre cohérent global. Pour simplifier cela, vous devriez essayer de vous assurer que tous les serrures qu'un fil aura besoin est acquis comme une seule opération. Si un fil doit acquérir des serrures A, B et C, il ne faut pas faire trois appels verrouillage () à des moments différents et de différents endroits. Vous serez confus et vous ne pourrez pas garder une trace dont les serrures sont conservées par le fil et lesquelles il n'a pas encore acquérir, puis vous allez gâcher la commande. Si vous pouvez acquérir tout le verrou, vous avez besoin une fois , vous pouvez extraire un appel de fonction distinct qui acquiert n serrures et le fait dans le bon ordre pour éviter les blocages.

Il y a ensuite les approches les plus ambitieuses: techniques telles que CSP Faire du filetage extrêmement simple et facile Pour prouver correctement, même avec des milliers de fils simultanés. Mais cela vous oblige à structurer votre programme de manière très différente de ce que vous êtes habitué.

Mémoire transactionnelle est une autre option prometteuse et qui peut être plus facile à intégrer dans conventionnel programmes. Mais les implémentations de qualité de production sont encore très rares.


0 commentaires

0
votes

Si vous souhaitez attaquer la possibilité d'une impasse, vous devez attaquer l'une des 4 conditions cruciales pour l'existence d'une impasse.

Les 4 conditions d'une impasse sont les suivantes: 1. Exclusion mutuelle - Un seul thread peut entrer dans la section critique à la fois. 2. Tenir et attendre - un thread ne libère pas les ressources qu'il a acquises tant qu'il n'a pas terminé son travail, même si d'autres ressources sont disponibles. 3. Aucune préemption - Un thread n'a pas de priorité sur d'autres threads. 4. Cycle de ressources - Il doit y avoir une chaîne de threads à cycle qui attend les ressources d'autres threads.

La condition la plus facile à attaquer est le cycle de ressources en veillant à ce qu'aucun cycles n'est possible.


0 commentaires