6
votes

Comment faites-vous déboguer efficacement des problèmes de comptage de référence dans la mémoire partagée?

suppose que vous avez un objet de référence compté dans la mémoire partagée. Le nombre de références représente le nombre de processus à l'aide de l'objet, et les processus sont responsables de l'incrémentation et de la décrémentation du nombre de comptes via des instructions atomiques, de sorte que le nombre de références elle-même est également dans la mémoire partagée (il pourrait s'agir d'un champ de l'objet, ou de l'objet. Peut contenir un pointeur au compte, je suis ouvert aux suggestions s'ils contribuent à résoudre ce problème). De temps en temps, un processus aura un bogue qui l'empêche de décrémenter le compte. Comment le rendez-vous aussi facile que possible de comprendre quel processus ne décrément pas le comte?

Une solution que j'ai pensée à donner à chaque processus un UID (peut-être leur PID). Ensuite, lorsque les processus décréments, ils poussent leur UID sur une liste liée stockée à côté du nombre de référence (j'ai choisi une liste liée car vous pouvez également ajouter atmoralement à la tête avec CAS ). Lorsque vous souhaitez déboguer, vous avez un processus spécial qui examine les listes liées des objets toujours en vie dans la mémoire partagée et que les UID des applications ne figurent pas dans la liste sont celles qui n'ont pas encore été décrites le comte.

L'inconvénient de cette solution est qu'il a une utilisation de la mémoire O (n) où n est le nombre de processus. Si le nombre de processus utilisant la zone de mémoire partagée est grand et que vous avez un grand nombre d'objets, cela devient rapidement très coûteux. Je soupçonne qu'il pourrait y avoir une solution à mi-chemin où, avec des informations de taille fixe partielle que vous pourriez aider à déboguer en étant capable de réduire la liste des processus possibles, même si vous ne pouviez pas identifier un seul. Ou si vous pouviez simplement détecter quel processus n'a pas décrémé lorsque seul un processus unique n'a pas (c'est-à-dire impossible de gérer la détection de 2 processus ou plus, ne pas décrémenter le compte) qui serait probablement toujours une grande aide.

(il existe plus de solutions «humaines» à ce problème, comme pour vous assurer que toutes les applications utilisent la même bibliothèque pour accéder à la région de mémoire partagée, mais si la zone partagée est traitée comme une interface binaire et que tous les processus ne seront pas réalisés. Applications écrites par vous qui est hors de votre contrôle. Également, même si toutes les applications utilisent la même bibliothèque, une application pourrait avoir un bogue en dehors de la mémoire de corruption de la bibliothèque de manière à éviter de décrémenter le compte. Oui, j'utilise un langage dangereux comme C / C ++;)

Edit: Dans des situations de processus unique, vous aurez le contrôle, vous pouvez donc utiliser Raii (en C ++).


7 commentaires

Une fois que j'ai compris comment déboguer efficacement des problèmes de comptage de référence dans la mémoire régulière (processus unique), je vais passer à ce problème.


C'est un problème très difficile que DCOM n'a jamais eu une bonne solution à


@Terry: Dans une situation de processus unique, vous pouvez utiliser Raii.


N'utilisez jamais de mémoire partagée pour quoi que ce soit - utilisez un processus de serveur.


@Neil: copiez donc toujours un objet N fois au lieu de stocker la référence informatique comptée une fois? ;) Les réponses «Jamais» ne semblent jamais pratiques;) Lorsque vous utilisez une langue sans danger de type véritablement (pas c), vous n'avez pas à vous soucier de la corruption de la mémoire, donc je ne pense même pas que ce soit nécessairement ce dangereux.


@Joseph Votre "langue vraiment sûre" (dont il n'y en a pas) sera écrit en C - donc oui, vous devez vous inquiéter. Mais vous allez de l'avant - L'expérience est un excellent enseignant, même si cela peut être coûteux.


@Neil: C'est vrai pour certaines langues «sûres» populaires (par exemple Java) mais n'est pas vraie en général. Haskell et Ocaml sont auto-étrangères de même que de nombreuses autres langues. En tout état de cause, il y a généralement des chances de faire des bugs dans votre heure d'exécution de la langue. Nous comptons sur des compilateurs eux-mêmes pour ne pas avoir de bugs tout le temps, je ne vois pas comment c'est différent. Vous devez peser le risque de votre application (je n'écris pas le système de soutien de la vie pour la navette spatiale pour enfants).


5 Réponses :


1
votes

L'État partagé par la mémoire partagée ne constitue pas une solution robuste et je ne sais pas d'une façon de le rendre robuste.

En fin de compte, si un processus sort, toutes ses ressources non partagées sont nettoyées par le système d'exploitation. Ceci est accidentellement la grosse victoire de l'utilisation de processus (fourchette ()) au lieu de threads.

Cependant, les ressources partagées ne sont pas. Poignées de fichier que d'autres sont ouvertes ne sont évidemment pas fermées et ... Mémoire partagée . Les ressources partagées ne sont fermées que lorsque le dernier processus leur partageait les sorties.

Imaginez que vous avez une liste de PID dans la mémoire partagée. Un processus pourrait analyser cette liste à la recherche de zombies, mais les PID peuvent alors être réutilisés, ou l'application aurait pu être suspendue plutôt que de se bloquer, ou ...

Ma recommandation est que vous utilisez des tuyaux ou d'autres messages qui passent des primitives entre chaque processus (parfois, il existe une relation de maîtrise naturelle-esclave, d'autres fois, tous ont tous besoin de parler à tous). Ensuite, vous profitez du système d'exploitation qui ferme ces connexions lorsqu'un processus meurt, et vos pairs sont donc signalés dans cet événement. De plus, vous pouvez utiliser des messages de timeout ping / pong pour déterminer si un pair a accroché.

Si, après profilage, il est trop inefficace pour envoyer les données réelles dans ces messages, vous pouvez utiliser une mémoire partagée pour la charge utile tant que vous gardez le canal de commande sur une sorte de flux que le système d'exploitation s'efforce. < / p>


2 commentaires

+1 pour l'avertissement contre l'optimisation prématurée (la réponse à beaucoup de questions sur SO!)


Les cas d'utilisation sont rares :) Mais parfois, j'ai besoin de quelques processus pour partager un blob de la taille d'un gigaoctet pour travailler en coopération - mais c'est vraiment l'exception, dans presque tous les cas, l'IPC basé sur l'événement ASYNC est tout ce qu'il faut :) :)



1
votes

Les systèmes de traçage les plus efficaces pour la propriété des ressources n'utilisent même pas les comptes de référence, sans parler des listes de détenteurs de référence. Ils ont simplement des informations statiques sur les dispositions de chaque type de données qui pourraient exister en mémoire, également la forme du cadre de pile pour chaque fonction, et chaque objet a un indicateur de type. Ainsi, un outil de débogage peut analyser la pile de chaque thread et suivre des références à des objets récursives jusqu'à ce qu'il ait une carte de tous les objets de la mémoire et comment ils se réfèrent à l'autre. Mais de systèmes de bien sûr que cette capacité dispose également de la collection automatique des ordures. Ils ont besoin d'aide du compilateur pour gagner toutes ces informations sur la disposition des objets et des cadres de pile, et de telles informations ne peuvent pas être obtenues de manière fiable de C / C ++ dans tous les cas (car les références d'objet peuvent être stockées dans les syndicats, etc.) sur le De plus, ils fonctionnent bien mieux que le comptage de référence au moment de l'exécution.

par votre question, dans le cas "dégénéré", tous (ou presque tous) de l'état de votre processus seraient détenus dans une mémoire partagée - à part des variables locales sur la pile. Et à ce stade, vous auriez l'équivalent exact d'un programme multi-threadé dans un processus unique. Ou de la mettre d'autre moyen, des processus qui partagent suffisamment de mémoire commencent à se distinguer des threads.

Cela implique que vous ne devez pas spécifier la partie de votre question "multiples processus, mémoire partagée". Vous faites face au même problème que quiconque visages quand ils essaient d'utiliser le comptage de référence. Ceux qui utilisent des threads (ou font une utilisation sans retenue de la mémoire partagée; la même chose) font face à un autre ensemble de problèmes. Mettez les deux ensemble et vous avez un monde de douleur.

En termes généraux, il est bon conseil de ne pas partager d'objets mutables entre les threads, dans la mesure du possible. Un objet avec un nombre de référence est mutable, car le nombre peut être modifié. En d'autres termes, vous partagez des objets mutables entre les threads (efficaces).

Je dirais que si votre utilisation de la mémoire partagée est suffisamment complexe pour avoir besoin de quelque chose de ressemblant à GC, vous avez presque le pire des deux mondes: le coûteux de la création de processus sans les avantages de l'isolement du processus. Vous avez écrit (en vigueur) une application multi-threadée dans laquelle vous partagez des objets mutables entre les threads.

Les prises locales sont une API très plate-forme et très rapide pour la communication interprocessée; Le seul qui travaille fondamentalement de manière identique sur tous les unes et fenêtres. Alors, envisagez d'utiliser cela comme un canal de communication minimal.

D'ailleurs, utilisez-vous de manière cohérente à l'aide des pointeurs intelligents dans les processus qui contiennent des références? C'est votre seul espoir d'être référencé à compter une moitié à droite.


2 commentaires

J'utilise le comptage de référence pour les entités mutables partagées et les valeurs immuables en cours et les prises pour l'événement ASYN basée tout le temps. D'ailleurs. Ref Comptage ne fait pas d'instances partagées sémantiquement mutable, car les opérations atomiques le rendent simplement au travail.


Mais de temps en temps, je dois partager un blob de la taille d'un gigaoctet entre des processus pour y travailler en collaboration. Et sur ces rares occasions, le comptage de référence croisé fonctionne comme un charme, jusqu'à ce qu'il ne soit pas. Ensuite, la situation doit être débogué :) Par conséquent, je trouve l'exigence rare mais en aucun cas exotique :) :)



8
votes

Vous pouvez le faire en utilisant uniquement un seul entier supplémentaire par objet.

initialise l'entier à zéro. Lorsqu'un processus incrémente le nombre de références pour l'objet, il xors son pid dans l'entier: xxx

lorsqu'un processus diminue le nombre de références, il fait de même.

Si le nombre de références est toujours laissé à 1, le tracker entier sera égal au PID du processus qui l'incrémentait mais ne l'a pas décédé.


Ceci fonctionne parce que Xor est commutatif ( (a ^ b) ^ c == a ^ (b ^ c) ), donc si un processus xors xors le suiveur avec son propre nombre de fois, c'est la même chose que le xorant avec pid ^ pid - c'est zéro, ce qui laisse la valeur de suivi non affectée.

Vous pouvez également utiliser une valeur non signée (qui est définie sur enveloppe plutôt que trop de débordement) - Ajout du PID lors de l'incrémentation du nombre d'usages et de la soustraits lors de la décrémentation.


6 commentaires

HAH, j'ai eu la même pensée que je me suis alors convaincu que j'avais tort avec un contre-exemple mathématique mental incorrect. Si vous mettez à jour pour expliquer pourquoi cela fonctionne (XOR est évidemment commutatif et X ^ x se nourrit) je vais marquer comme accepté :)


Je suis généralement sceptique des hacks de manipulation de bits, mais celui-ci est tifty. Agréable!


Ne pas ajouter et soustraire d'être erroné? Dis que j'ai 3 processus de telle que le PID1 + PID2 = PID3. Si tous les 3 sont incrémentés et des décréments de PID3, le tracker sera de 0 mais le compte Ref est toujours 2


@hackworks: Pas exactement - PID3 ne décrétera pas la valeur que si cela ne l'a pas incrémenté plus tôt. Cependant, il est possible pour pid1 + pid2 = 0 laissant le tracker 0 avec un nombre de référence de 2 - mais cela n'a pas d'importance, car ce n'est que lorsque le nombre de Ref est 1 que nous examinons le tracker valeur.


@CAF Je vois un problème sur le débogage. Si je veux savoir quels processus n'ont pas décrémenté, je vais me tromper. J'ai besoin de suivre une fuite de ressource et de trouver l'identifiant (c'est dans la couche réseau - ID de connexion) de la connexion qui saute en décrétant la référence.


@hackworks: La réponse ici est d'identifier efficacement le processus lorsque le processus un n'a pas réussi à décrémenter le nombre de références. Si vous avez un nombre arbitraire de processus, votre seule solution est une liste.



0
votes

Utilisez après xxx

incrément xxx

décrément xxx

Donc Connaissez tous les processus à l'aide de cet objet

vous max_procs assez gros et rechercher gratuitement Placez au hasard, donc si le nombre de processus signifimentez inférieur, alors max_procs la recherche serait très rapide.


0 commentaires

0
votes

À côté de vous faire vous-même: vous pouvez également utiliser un outil tel que AQTime, qui a une référence compté Memchecker.


0 commentaires