2014-05-17 1 views
5

Quelles sont les bonnes pratiques pour éviter les conditions de course dans Go? Le seul que je peux penser n'est pas le partage de données entre goroutines - le goroutine parent envoie une copie profonde d'un objet plutôt que l'objet lui-même, ainsi le goroutine enfant ne peut pas muter quelque chose que le parent peut. Cela utiliserait plus de mémoire de tas, mais l'autre alternative est d'apprendre Haskell: PGolang: éviter les conditions de course

Éditer: aussi, y a-t-il un scénario dans lequel la méthode que j'ai décrite ci-dessus peut toujours se heurter aux conditions de course?

+1

Les conditions de course sont difficiles à déboguer, il est également difficile de les maîtriser en les évitant. Je suggère de réfléchir sur les modèles décrits ici par Rob Pike http://vimeo.com/49718712 alors qu'il passe en revue certaines constructions de la façon dont les canaux et la sémantique CSP rendent une application sûre par conception au lieu de se préoccuper de tous les problèmes associés aux mutex. Je suis désolé si cela ne répond pas à votre question mais j'espère que cela ouvrira plus de portes à de nouvelles idées. – ymg

+0

Merci pour le lien. Oui, les conditions de course sont difficiles à empêcher, mais encore plus difficiles à déboguer! Je suppose qu'une version immuable des collections permettrait d'atténuer le problème dans une certaine mesure, mais nous sommes de retour dans la cour de la programmation fonctionnelle. – tldr

+0

Il existe d'autres langages fonctionnels/immuables (comme F #), mais il s'agit vraiment de style. Vous avez raison d'avoir besoin de plus de mémoire, mais une conception appropriée peut éliminer certains frais généraux. Par exemple, les tableaux d'écriture sur modification peuvent être faits dans l'espace 'n log n' (ou moins pour certains cas potentiels) en moyenne, plutôt que naïf' 2n'. Bien que le support de langage/optimiseur puisse faire des choses folles pour les langages construits à cet effet ... –

Répondre

13

Les conditions de course peuvent certainement toujours exister même avec des structures de données non partagées. Considérez ce qui suit:

B asks A for the currentCount 
C asks A for the currentCount 
B sends A (newDataB, currentCount + 1) 
A stores newDataB at location currentCount+1 
C sends A (newDataC, currentCount + 1) 
A stores newDataC at currentCount + 1 (overwriting newDataB; race condition) 

Cette condition de course exige état mutable privé dans A, mais pas de structures de données partagées mutables et n'a même pas besoin d'état mutable en B ou C. Il n'y a rien B ou C peuvent faire pour prévenir cette condition de course sans comprendre le contrat qu'offre A.

Même Haskell peut souffrir de ce genre de conditions de concurrence dès que l'état entre dans l'équation, et l'état est très difficile à éliminer complètement d'un système réel. Finalement, vous voulez que votre programme interagisse avec la réalité, et la réalité est dynamique.Wikipedia donne a helpful race condition example in Haskell en utilisant STM. Je suis d'accord que de bonnes structures de données immuables pourraient rendre les choses plus faciles (Go n'en a pas vraiment). Les copies mutables échangent un problème pour un autre. Vous ne pouvez pas modifier accidentellement les données de quelqu'un d'autre. D'un autre côté, vous pouvez penser que vous changez le vrai, alors que vous changez simplement une copie, menant à un autre type de bogue. Vous devez comprendre le contrat de toute façon. Mais finalement, Go a tendance à suivre l'histoire de C sur la concurrence: vous créez des règles de propriété pour votre code (comme les offres @ tux21b) et assurez-vous de toujours les suivre, et si vous le faites parfaitement, ça va tout fonctionne bien, et si jamais vous faites une erreur, alors c'est évidemment votre faute, pas la langue.

(Ne vous méprenez pas, j'aime Go, beaucoup vraiment, et il offre de bons outils pour faciliter la concurrence, mais il n'offre pas beaucoup d'outils de langage pour aider à faire la simultanéité corriger. Cela dit, la réponse de tux21b offre beaucoup de bons conseils, et le détecteur de course est certainement un outil puissant pour réduire les conditions de course, il ne fait pas partie du langage, il est question de test, pas de correction, ce n'est pas le même chose.)

EDIT: À la question de savoir pourquoi les structures de données immuables rendent les choses plus faciles, c'est l'extension de votre point initial: la création d'un contrat où plusieurs parties ne changent pas le même dat une structure. Si la structure de données est immuable, alors cela vient gratuitement ...

De nombreuses langues ont un riche ensemble de collections et de classes immuables. C++ vous laisse const à peu près tout. Objective-C possède des collections immuables avec des sous-classes mutables (ce qui crée un ensemble de modèles différent de const). Scala a des versions mutables et immuables séparées de nombreux types de collection, et il est courant d'utiliser exclusivement les versions immuables. La déclaration de l'immutabilité dans la signature d'une méthode est une indication importante du contrat.

Lorsque vous passez un []byte à un goroutine, il n'y a aucun moyen de savoir à partir du code si le goroutine a l'intention de modifier la tranche, ni quand vous pouvez modifier la tranche vous-même. Il y a des modèles qui émergent, mais ils sont comme la propriété d'un objet C++ avant la sémantique de déplacement; beaucoup d'approches fines, mais pas moyen de savoir lequel est utilisé. C'est une chose critique que chaque programme doit faire correctement, pourtant le langage ne vous donne pas de bons outils, et il n'y a pas de modèle universel utilisé par les développeurs.

+1

Ceci est une excellente réponse. Pourriez-vous élaborer un peu sur la façon dont les structures de données immuables rendent les choses plus faciles? – tldr

6

La commande Go n'impose pas la sécurité de la mémoire de manière statique. Il existe plusieurs façons de gérer le problème même dans les bases de code importantes, mais toutes requièrent votre attention.

  • Vous pouvez envoyer des pointeurs, mais un idiome commun consiste à signaler le transfert de propriété en envoyant un pointeur. Par exemple, une fois que vous passez le pointeur d'un objet à un autre Goroutine, vous ne le touchez plus, sauf si vous récupérez l'objet de ce goroutin (ou de tout autre Goroutine si l'objet est passé plusieurs fois) par un autre signal.

  • Si vos données sont partagées par de nombreux utilisateurs et ne changent pas si souvent, vous pouvez partager un pointeur vers ces données globalement et autoriser tout le monde à en lire. Si un Goroutine veut le changer, il doit suivre l'idiome copy-on-write, c'est-à-dire copier l'objet, muter les données, essayer de placer le pointeur sur le nouvel objet en utilisant quelque chose comme atomic.CompareAndSwap. L'utilisation d'un Mutex (ou d'un RWMutex si vous voulez autoriser plusieurs lecteurs simultanés à la fois) n'est pas si grave. Bien sûr, un Mutex n'est pas une solution miracle et il est souvent mal adapté à la synchronisation (et il est surutilisé dans de nombreuses langues qui mènent à sa mauvaise réputation), mais c'est parfois la solution la plus simple et la plus efficace.

Il existe probablement de nombreuses autres façons. Envoyer des valeurs uniquement en les copiant en est une autre et facile à vérifier, mais je pense que vous ne devriez pas vous limiter à cette méthode seulement. Nous sommes tous matures et nous sommes tous capables de lire la documentation (en supposant que vous documentiez correctement votre code).

L'outil Go est également livré avec un très bon race detector intégré, capable de détecter les courses lors de l'exécution. Ecrire beaucoup de tests et les exécuter avec le détecteur de course activé et prendre chaque message d'erreur au sérieux. Ils indiquent généralement un design mauvais ou compliqué. (PS: Vous pouvez jeter un oeil à Rust si vous voulez un compilateur et un système de type qui est capable de vérifier l'accès simultané au moment de la compilation, tout en permettant l'état partagé, je ne l'ai pas utilisé moi-même, mais les idées semblent très prometteuses.)

+0

Ce sont tous des points valides, et ils fonctionneraient si j'écrivais le code entier moi-même. Mais, si mon goroutine exécute une fonction à partir d'une bibliothèque externe, je devrais passer par son implémentation pour m'assurer que le développeur de la bibliothèque respecte également le contrat.Je pense que le fait de ne pas partager d'état rend beaucoup plus facile de me protéger des externalités. Y a-t-il un scénario où ma solution proposée pourrait encore rencontrer des conditions de course? – tldr

Questions connexes