8
votes

Stalles de Valgrind dans le programme de socket multithreaded

Je gère un programme de prise multithreau avec Valgrind. Le client enverra une demande au serveur sur TCP, puis occupé à attendre sur un booléen. Le booléen sera défini lorsque la fonction de rappel qui fournit la réponse du serveur est appelée. Une fois la réponse reçue (et le drapeau booléen est défini), le serveur envoie à nouveau une demande et le faire à plusieurs reprises dans une boucle.

Je me rends compte que l'accès non synthronisé aux variables partagées (le booléen) peut causer des problèmes de filetage, mais j'ai essayé d'utiliser des mutiles Pthread et que le programme ralentit d'environ 20% (la vitesse est importante ici). Je suis convaincu que la rédaction de la variable booléenne commune va bien comme cela peut être fait en un seul cycle.

Le programme fonctionne bien en dehors du Valgrind, mais stalle souvent lorsqu'il est exécuté avec Valgrind. J'ai quitté le programme pour courir pendant la nuit .. Habituellement, il faut quelques secondes à compléter, alors je ne pense pas que ce soit un cas de ne pas attendre assez longtemps pour que le programme soit terminé. Le threading est géré par le cadre de moteur open source (solution rapide), donc je ne pense pas que ce soit un problème avec la manière dont les threads sont créés / gérés.

Est-ce que quelqu'un connaît-il des problèmes de valorisation autour de programmes multi-filetés / boucles d'attente occupées / communications de socket (ou une combinaison de ces)?


3 commentaires

En attente d'une variable booléenne commune n'est pas "fait dans un seul cycle", cela se fait dans plusieurs cycles par boucle d'itération, et si votre boucle occupée attend un aller-retour de TCP sur le réseau, la boucle est probable. Aller à itérer plusieurs milliards de fois (et donc gaspiller plusieurs milliards de cycles de CPU qui auraient pu être mieux utilisés ailleurs). Une meilleure solution que l'une des choses que vous avez mentionnées serait d'attendre une variable de condition et que la fonction de rappel signalez-vous la variable de condition pour réveiller votre enfileur lorsque les données sont prêtes.


J'imprimante que l'écriture à la variable booléenne est effectuée en un seul cycle (pas l'ensemble du processus d'attente occupé). Cela dit que j'aurais dû dire que l'écriture à la variable booléenne est effectuée atomiquement (comme cache misses, etc. peut pousser une écriture d'un seul octet après un seul cycle de la CPU)


Ce que Jeremy a dit - attendez-vous, c'est une mauvaise idée, la variable de condition est meilleure et peu susceptible d'être plus lente ...


4 Réponses :


3
votes

lire / écrire un booléen n'est pas une opération atomique sur x86.

Voir ma question ici: Est volatile une bonne façon de créer un seul octet atomique en C / C ++?


1 commentaires

La réponse indique que, pour la plupart d'une rédaction d'un booléen, est atomique: "Les CPU ont généralement lu et écrivent atomiquement des octets isolés." À partir des réponses de la question, le problème n'est pas une atomicité, c'est le moment où tous les processeurs voient la nouvelle valeur.



2
votes

Même si écrire votre booléen est une opération atomique, le compiler et La CPU est libre de ré-commander la mise à jour autour d'autres accessoires de mémoire. Votre fil d'attente occupé peut éveillé de la boucle occupée et découvrez que la structure de données partagée a pas effectivement été mise à jour.

Je recommande vivement de coller aux primitives de filetage à votre disposition pour écrire des programmes cohérents conformes exactement comme vous le souhaitez, à chaque fois.


2 commentaires

La seule chose partagée entre les deux threads est le drapeau booléen (il est utilisé comme signal). Le fil d'attente occupé ne se réveille que si le drapeau a été défini, ce qui signifie que le changement de drapeau doit se propager à la CPU qui exécute le fil d'attente occupé. Compte tenu de cela, le code comme décrit ne fonctionne toujours pas correctement?


Heh, avez-vous essayé signaux? Cela pourrait faire l'affaire. :)



10
votes

tandis que d'autres réponses se concentrent sur l'insistance sur laquelle vous prenez l'approche de synchronisation standard (quelque chose que je suis tout à fait d'accord avec), je pensais plutôt que je devrais répondre à votre question concernant Valgrind.

Autant que je sache, il n'y a pas de problèmes avec Valgrind en cours d'exécution dans un environnement multi-fileté. Je crois que Valgrind force l'application à courir sur un seul noyau, mais autre que cela ne devrait pas affecter vos discussions.

Ce que le valcrind fait probablement à votre application modifie les horaires et les interactions entre vos threads d'une manière qui pourrait exposer des bogues et des conditions de course dans votre code que vous ne voyez pas normalement tout en fonctionnant autonome.

La même logique que vous avez appliquée pour décider que le bogue n'a pas pu être dans le cadre de filetage open source que vous utilisez s'applique également à Valgrind à mon avis. Je vous recommande de considérer que ceux-ci sont suspendus comme des bugs dans votre code et de les déboguer en tant que tel, car c'est probablement ce qu'ils sont.

Comme une note latérale, l'utilisation d'un mutex est probablement survenue pour le problème que vous avez décrit. Vous devez étudier à la place des sémaphores ou des variables de condition.

bonne chance.


2 commentaires

Pas entièrement convaincu qu'il y a des bugs dans mon code, mais votre message est noté. Note latérale - J'ai examiné les variables de condition à l'aide de la bibliothèque Pthread, ce qui nécessite un mutex pour arrêter les conditions de race sur la condition réelle elle-même.


@Taras: correct sur la variable de condition. Les sémaphores seraient ma préférence dans ce cas. Beaucoup plus léger que les mutiles. Sur le problème de suspension, pourquoi ne pas inspecter le processus suspendu avec GDB pour découvrir ce que fait?



6
votes

Je viens d'avoir un problème similaire. Comme l'op, j'avais un fil d'attente occupé. Dans mon cas, le problème était que l'attente occupée prenait presque tous les cycles de la CPU et que les autres threads couraient plusieurs milliers de fois plus lents. Au début, j'ai réparé cela en mettant un usep (1) dans la boucle d'attente occupée (uniquement pour Valgrind Builds). Ensuite, j'ai lu le manuel Valgrind et appris de l'option - FAIR-SCHAND = YES , qui a également corrigé le problème et m'a permis de supprimer le USLEEP (1 ) .


0 commentaires