2009-06-15 8 views
0

Je me demande quel est le «meilleur» moyen de sécuriser les données. Plus précisément, j'ai besoin de protéger une liste chaînée sur plusieurs threads - un thread peut essayer de lire à partir de celui-ci tandis qu'un autre thread ajoute/supprime des données, ou même libère la liste entière. J'ai lu sur les serrures; ils semblent être l'approche la plus couramment utilisée, mais apparemment ils peuvent être problématiques (deadlocks). J'ai également lu sur les opérations atomiques ainsi que sur le stockage local des threads. À votre avis, quel serait mon meilleur plan d'action? Quelle est l'approche que la plupart des programmeurs utilisent, et pour quelle raison?La sécurité des fils ... quel est mon "meilleur" plan d'action?

+2

meilleure approche dépend de la langue que vous utilisez. Par exemple, dans erlang c'est un non-problème –

+0

yep, le renvoi de message d'erlang _intrinsically_ (donc le multitraitement est le plus doux) - dans ma réponse j'ai esquissé comment construire cet état heureux dans d'autres langages tels que Python. –

Répondre

5

Une approche qui est peu utilisé, mais tout à fait le son, est de désigner un fil à usage spécial de posséder toutes les structures « partagée ». Ce thread se trouve généralement en attente sur une file d'attente (thread-safe ;-), par ex. dans Python une instance Queue.Queue, pour les demandes de travail (lecture ou modification de la structure partagée), incluant les deux qui demandent une réponse (ils passeront leur propre file sur laquelle la réponse est placée quand ils sont prêts) et ceux qui ne le font pas. Cette approche sérialise entièrement tout accès à la ressource partagée, remappe facilement à une architecture multi-processus ou distribuée (presque sans cervelle, en Python, avec multiprocessing ;-), et garantit absolument la solidité et l'absence d'interblocages ainsi que des conditions de course aussi longtemps que l'objet de la file d'attente sous-jacente est bien programmé une fois pour toutes.

Il transforme fondamentalement l'enfer des structures de données partagées dans le paradis des architectures de concurrence de passage de message.

OTOH, il peut être être un peu plus haut-frais généraux que slugging sur le chemin difficile avec des serrures & c ;-).

+0

d'accord. Je préfère passer à une interface basée sur le passage de messages plutôt que de prendre en charge les ajouts/suppressions simultanés ET la traversée sur une liste chaînée. – Rick

+0

Vous devez toujours faire attention aux blocages dans un système de transfert de message pur ... si le processus A attend le message b du processus B avant d'envoyer le message a pour traiter le processus C -AND- B n'envoie le message b que lorsque il reçoit le message c du processus C -AND- le processus C n'émet que le message c lorsqu'il reçoit le message a. Le même problème de base qu'un blocage ne dépend que des dépendances de messagerie au lieu de ressources verrouillées. –

+0

Dans l'API Windows, il s'agit de la différence entre SendMessage (qui est synchrone et qui bloque l'expéditeur jusqu'à ce que l'envoi ait été reçu et traité) et PostMessage (qui est asynchrone). La transmission de messages asynchrone n'est pas sujette à l'impasse (mais vous pourriez plutôt vous demander si votre message a effectivement été reçu et traité). – ChrisW

0

Rappelez-vous toujours la règle la plus importante de la sécurité du filetage. Connaissez toutes les sections critiques de votre code à l'envers. Et par là, connais-les comme ton ABC. Seulement si vous pouvez les identifier à partir une fois demandé, vous savez sur quelles zones à utiliser vos mécanismes de sécurité de fil sur.

Après cela, rappelez-vous les règles de base:

  • Regardez pour toutes vos globales variables/variables sur le tas.
  • Assurez-vous que vos sous-programmes sont re-entrant.
  • Assurez-vous que l'accès aux données partagées est sérialisé.
  • Assurez-vous qu'il n'y a pas d'accès indirect via des pointeurs.

(je suis sûr que d'autres peuvent ajouter d'autres.)

0

La meilleure façon, du point de vue de la sécurité, est de verrouiller toute la structure de données, de sorte qu'un seul thread puisse la toucher à la fois. Une fois que vous décidez de verrouiller moins que la totalité de la structure, vraisemblablement pour des raisons de performance, les détails sont compliqués et diffèrent pour chaque structure de données, et même pour les variantes de la même structure.

Ma suggestion est de

  1. Commencez avec un verrou global sur votre structure de données. Profil votre programme pour voir si c'est vraiment un problème.

  2. En cas de problème, déterminez s'il existe un autre moyen de distribuer le problème. Pouvez-vous minimiser la quantité de données dans la structure de données en question, de sorte qu'il n'est pas nécessaire d'y accéder aussi souvent ou si longtemps? S'il s'agit d'un système de mise en file d'attente, par exemple, vous pouvez peut-être conserver une file d'attente locale par thread et déplacer uniquement les éléments d'une file d'attente globale lorsqu'une file d'attente locale est surchargée ou sous-chargée.

  3. Examinez les structures de données conçues pour aider à réduire la contention pour le type particulier de choses que vous faites, et à les mettre en œuvre avec soin et précision, en privilégiant la sécurité. Pour l'exemple de mise en file d'attente, les files d'attente de vol peuvent être ce dont vous avez besoin.

+0

Cette approche suppose également qu'une «structure entière» est autonome et que vous n'avez jamais besoin de verrouiller plus d'un objet à la fois. Normalement, le problème des verrouillages globaux à la structure se produit lorsque les données dans une structure pointent vers des données dans une autre structure. Puis les blocages deviennent un lieu commun. – jmucchiello

+0

En effet, si vous avez besoin de verrouiller plusieurs structures et que vous utilisez plusieurs verrous, plutôt qu'un verrou pour les verrouiller tous en même temps, vous êtes dans la même position que si vous utilisiez plusieurs verrous pour verrouiller différentes parties d'une structure de données. –

2

Vous pourriez envisager une collection immutable. Tout comme une chaîne dans .net a des méthodes telles que Replace, Insert, etc. Elle ne modifie pas la chaîne mais en crée une nouvelle, une collection LinkedList peut être conçue pour être également immuable. En fait, LinkedList est en fait assez simple à mettre en œuvre de cette façon par rapport à d'autres structures de données de collection.

Voici un lien vers un article de blog sur les collections immuables et un lien vers certaines implémentations dans .NET.

http://blogs.msdn.com/jaredpar/archive/2009/04/06/immutable-vs-mutable-collection-performance.aspx

+0

Ces types de structures de données sont souvent connus sous le nom de structures de données "persistantes" (et il existe une balise 'persist-data-structures' sur SO pour les discussions à ce sujet). Ceux-ci peuvent souvent partager des cellules de mémoire avec des versions antérieures des structures de données, ce qui réduit la copie (et l'utilisation de la mémoire, si vous conservez les versions précédentes des structures de données). –

Questions connexes