Concurrence: comment abordez-vous la conception et déboguez-vous l'implémentation?


37

Je développe des systèmes simultanés depuis plusieurs années maintenant, et je connais assez bien le sujet malgré mon manque de formation formelle (c’est-à-dire qu’il n’ya pas de diplôme). Il y a quelques nouveaux langages qui sont devenus populaires pour au moins parler de ces derniers temps et qui sont conçus pour faciliter la concurrence, tels que Erlang et Go. Il semble que leur approche de la simultanéité fasse écho à ma propre expérience quant à la manière de rendre des systèmes évolutifs et de tirer parti de plusieurs cœurs / processeurs / machines.

Cependant, je trouve qu'il existe très peu d'outils pour vous aider à visualiser ce que vous avez l'intention de faire et à vérifier que vous êtes au moins près de votre vision d'origine. Déboguer du code concurrent peut être un cauchemar avec des langages qui ne sont pas conçus pour la concurrence (comme C / C ++, C #, Java, etc.). En particulier, il est presque impossible de recréer les conditions qui se produisent facilement sur un système dans votre environnement de développement.

Alors, quelles sont vos approches pour concevoir un système prenant en charge le traitement simultané et simultané? Exemples:

  • Comment déterminez-vous ce qui peut être rendu simultané par rapport à ce qui doit être séquentiel?
  • Comment reproduire des conditions d'erreur et voir ce qui se passe pendant l'exécution de l'application?
  • Comment visualisez-vous les interactions entre les différentes parties concourantes de l'application?

J'ai mes propres réponses à certaines de ces questions, mais j'aimerais aussi en apprendre un peu plus.

modifier

Jusqu'à présent, nous avons beaucoup de bonnes contributions. Beaucoup d'articles liés à sont très bons et j'en ai déjà lu quelques-uns.

Mon expérience personnelle avec la programmation concurrente m'amène à penser que vous avez besoin d'un état d'esprit différent de celui de la programmation séquentielle. La fracture mentale est probablement aussi large que la différence entre la programmation orientée objet et la programmation procédurale. J'aimerais que cette série de questions se concentre davantage sur les processus de pensée nécessaires (à savoir la théorie) pour aborder systématiquement les réponses. Lorsque vous fournissez des réponses plus concrètes, il est utile de donner un exemple - quelque chose que vous avez vécu personnellement.

But pour le Bounty

Ne me dis pas ce que je devrais faire. J'ai déjà cela sous contrôle. Dites-moi ce que vous faites. Dis-moi comment tu résous ces problèmes.


C'est une bonne question - beaucoup de profondeur possible. J'ai également acquis une bonne expérience des applications multithreads en Java, mais j'aimerais en savoir plus.
Michael K

Jusqu'à présent, nous avons quelques bonnes réponses. Quelqu'un veut-il essayer ce que vous souhaiteriez avoir pour vous aider dans ce domaine?
Berin Loritsch

TotalView Debugger pour le codage simultané est un outil très utile, mais prend un peu de temps à apprendre - totalviewtech.com/products/totalview.html
Fanatic23

Se connecter pourrait peut-être vous aider avec deux dernières questions.
Amir Rezaei

Ce que je recherche, ce sont les processus des gens. Ce sont des domaines dans lesquels les outils que j'ai utilisés sont inadéquats, mais peuvent faire le travail. Je suis moins préoccupé par le fait de citer l'article de quelqu'un d'autre que par la méthodologie.
Berin Loritsch

Réponses:


11

Je développe des systèmes simultanés depuis plusieurs années et je maîtrise assez bien le sujet malgré mon manque de formation formelle (c’est-à-dire qu’il n’ya pas de diplôme).

Beaucoup des meilleurs programmeurs que je connaisse n'ont pas terminé l'université. Quant à moi j'ai étudié la philosophie.

C / C ++, C #, Java, etc.). En particulier, il est presque impossible de recréer les conditions qui se produisent facilement sur un système dans votre environnement de développement.

Oui

Comment déterminez-vous ce qui peut être rendu simultané par rapport à ce qui doit être séquentiel?

nous commençons généralement par une métaphore de 1 000 km pour clarifier notre architecture pour nous-mêmes (d’une part) et pour les autres (d’autre part).

Face à ce problème, nous avons toujours trouvé le moyen de limiter la visibilité des objets concurrents à celle d'objets non concurrents.

Dernièrement, j'ai découvert Actors dans Scala et j'ai vu que mes anciennes solutions étaient une sorte de "miniacteurs", beaucoup moins puissants que ceux de Scala. Donc, ma suggestion est de partir de là.

Une autre suggestion consiste à éviter autant de problèmes que possible: nous utilisons par exemple un cache centralisé (terre cuite) au lieu de conserver les cartes en mémoire, des rappels de classe interne plutôt que des méthodes synchronisées, l'envoi de messages plutôt que l'écriture de mémoire partagée, etc.

De toute façon, avec Scala, c'est beaucoup plus facile.

Comment reproduire des conditions d'erreur et voir ce qui se passe pendant l'exécution de l'application?

Pas de vraie réponse ici. Nous avons quelques tests unitaires pour la simultanéité et une suite de tests de charge pour insister autant que possible sur l'application.

Comment visualisez-vous les interactions entre les différentes parties concourantes de l'application?

Encore une fois pas de vraie réponse: nous concevons notre métaphore sur le tableau blanc et nous essayons de nous assurer qu'il n'y a pas de conflits sur le plan architectural.

Pour Arch ici, je veux dire la définition de Neal Ford: Sw Architecture est tout ce qui sera très difficile à changer plus tard.

la programmation me porte à croire que vous avez besoin d'un état d'esprit différent de celui de la programmation séquentielle.

Peut-être, mais pour moi, il est tout simplement impossible de penser de manière parallèle, il est donc préférable de concevoir notre logiciel de manière à ne pas avoir à penser en parallèle et à utiliser des garde-corps clairs pour éviter les collisions entre les voies d'accès simultané.


6

Pour moi, tout est une question de données. Brisez vos données correctement et le traitement en parallèle est facile. Tous les problèmes de rétention, de blocages, et ainsi de suite.

Je sais que ce n'est pas le seul moyen de paralléliser, mais pour moi, c'est de loin le plus utile.

Pour illustrer une histoire (pas si rapide):

J'ai travaillé sur un grand système financier (contrôle boursier) de 2007 à 2009 et le volume de traitement des données était très important. À titre d’illustration, tous les calculs effectués sur un seul compte d’un client prennent environ 1 à 3 secondes sur leur poste de travail moyen, et il existe plus de 30 000 comptes. Chaque nuit, la fermeture du système était très pénible pour les utilisateurs (généralement plus de 6 heures de traitement, sans aucune marge d'erreur pour eux).

En étudiant davantage le problème, nous avons découvert que nous pouvions paralyser les calculs sur plusieurs ordinateurs, mais que nous aurions toujours un goulot d'étranglement sur l'ancien serveur de base de données (un serveur SQL 2000 émulant SQL 6.5).

Il était assez clair que notre paquet de traitement minimum était le calcul d'un compte unique et que le principal goulot d'étranglement était la rétention du serveur de base de données (nous pouvions voir sur le "sp_who" plusieurs connexions en attente d'effectuer le même traitement). Ainsi, le processus parallèle s'est déroulé comme suit:

1) Un seul producteur, chargé de lire la base de données ou d’écrire dessus, de manière séquentielle. Aucune concurrence autorisée ici. Le producteur a préparé une file d’emplois pour les consommateurs. La base de données appartenait uniquement à ce producteur.

2) Plusieurs consommateurs, sur plusieurs machines. Chacun des consommateurs a reçu un paquet complet de données, prêt de la file d'attente, à calculer. Chaque opération de dé-file est synchronisée.

3) Après le calcul, chaque consommateur a renvoyé au producteur les données dans une file d'attente synchronisée en mémoire afin de les conserver.

Il y avait plusieurs points de contrôle, plusieurs mécanismes pour assurer que les transactions étaient correctement sauvegardées (aucun n'était laissé derrière), mais tout le travail en valait la peine. En fin de compte, les calculs répartis sur 10 ordinateurs (plus l'ordinateur du producteur / de la file d'attente) ont réduit le temps de fermeture de l'ensemble du système à 15 minutes.

Supprimer les problèmes de rétention causés par la mauvaise gestion des accès simultanés SQL 6.5 nous avait donné un gros avantage. Le reste était à peu près linéaire, chaque nouvel ordinateur ajouté à la "grille" diminuant le temps de traitement, jusqu'à atteindre "l'efficacité maximale" des opérations de lecture / écriture séquentielles sur la base de données.


2

Travailler dans un environnement multi-threading est difficile et nécessite une discipline de codage. Vous devez suivre les directives appropriées pour verrouiller, relâcher le verrou, accéder aux variables globales, etc.

Permettez-moi d'essayer de répondre à votre question un par un

* How do you figure out what can be made concurrent vs. what has to be sequential?

Utiliser la concurrence pour

1) Polling: - il faut un thread pour interroger quelque chose en permanence ou envoyer la mise à jour régulièrement. (Des concepts tels que des cœur, qui envoient des données sur un intervalle régulier au serveur central pour dire que je suis en vie.)

2) Les opérations qui ont des entrées / sorties lourdes pourraient être effectuées en parallèle. Le meilleur exemple est enregistreur. Le thread de journalisation pourrait être un thread séparé.

3) Tâches similaires sur des données différentes. Si une tâche se produit sur des données différentes mais de nature très similaire, différents threads peuvent le faire. Le meilleur exemple sera les requêtes du serveur.

Et bien sûr, beaucoup d’autres comme celui-ci dépendent de l’application.

* How do you reproduce error conditions and view what is happening as the application executes?

L'utilisation des journaux et des impressions de débogage dans les journaux. Essayez de vous connecter également l'identifiant du thread afin que vous puissiez voir ce qui se passe dans chaque thread.
Une façon de générer une condition d'erreur consiste à placer le délai délibéré (dans le code de débogage) aux endroits où le problème se produit, et à arrêter énergiquement ce thread. Des opérations similaires peuvent également être effectuées dans les débogueurs, mais je ne l’ai pas encore fait.

* How do you visualize the interactions between the different concurrent parts of the application?

Placez les journaux dans vos serrures pour savoir qui verrouille quoi et quand et qui a essayé de le verrouiller. Comme je l'ai dit plus tôt, essayez de mettre l'identifiant de thread dans le journal pour comprendre ce qui se passe dans chaque thread.

C’est tout simplement mon conseil, qui consiste à travailler sur l’application multithread depuis environ 3 ans, et espère que cela aidera.


2
  • Comment déterminez-vous ce qui peut être rendu simultané par rapport à ce qui doit être séquentiel?

Je me demanderais d’abord si l’application (ou le composant) bénéficiera réellement d’un traitement simultané, ou en termes simples, où se situe le goulot d’étranglement? Évidemment, la concurrence n’apportera pas toujours un avantage pour l’investissement nécessaire à son bon fonctionnement. Si cela ressemble à un candidat, je travaillerais de manière ascendante - en essayant de trouver la plus grande opération ou l'ensemble des opérations pouvant faire son travail efficacement de manière isolée - je ne veux pas créer de fils pour des tâches insignifiantes et peu rentables. opérations - je cherche des acteurs .

En travaillant avec Erlang, je suis devenu un adepte absolu du concept d’utilisation du passage de messages asynchrone et du modèle d’acteur pour la simultanéité - c’est intuitif, efficace et clair.

Hors de Concurrency Comprendre Acteur

Le modèle d'acteur se compose de quelques principes clés:

  • Pas d'état partagé
  • Procédés légers
  • Passage de message asynchrone
  • Boîtes aux lettres pour mettre en mémoire tampon les messages entrants
  • Traitement de boîtes aux lettres avec correspondance de modèle

Un acteur est un processus qui exécute une fonction. Ici, un processus est un thread léger d'espace utilisateur (à ne pas confondre avec un processus typique de système d'exploitation lourd). Les acteurs ne partagent jamais d’État et n’ont donc jamais besoin de se faire concurrence pour obtenir des verrous d’accès aux données partagées. Au lieu de cela, les acteurs partagent des données en envoyant des messages immuables. Les données immuables ne peuvent pas être modifiées, les lectures ne nécessitent donc pas de verrouillage.

Le modèle de concurrence Erlang est plus facile à comprendre et à mettre au point que le verrouillage et le partage de données. La façon dont votre logique est isolée permet de tester facilement les composants en leur transmettant des messages.

Travailler avec des systèmes concurrents, c’est à peu près ainsi que ma conception fonctionnait de toute façon dans n’importe quel langage - une file d’attente à partir de laquelle plusieurs threads extraient des données, effectuent une opération simple, puis répètent ou repoussent dans la file d’attente. Erlang ne fait que renforcer les structures de données immuables pour prévenir les effets secondaires et réduire le coût et la complexité de la création de nouveaux threads.

Ce modèle n’est pas exclusif à Erlang, même dans le monde Java et .NET, il existe des moyens de le créer - j’examinerais le CCR (Concurrence and Coordination Runtime) et Relang (il existe également Jetlang pour Java).

  • Comment reproduire des conditions d'erreur et voir ce qui se passe pendant l'exécution de l'application?

D'après mon expérience, la seule chose que j'ai pu faire est de m'engager à tout tracer / enregistrer. Chaque processus / thread doit avoir un identifiant et chaque nouvelle unité de travail doit avoir un identifiant de corrélation. Vous devez être capable de parcourir vos journaux et de retracer exactement ce qui était en cours de traitement et à quel moment - je n’ai pas eu la magie de l’éliminer.

  • Comment visualisez-vous les interactions entre les différentes parties concourantes de l'application?

Voir ci-dessus, c'est moche mais ça marche. La seule autre chose que je fais est d’utiliser des diagrammes de séquence UML - bien sûr, c’est pendant la phase de conception - mais vous pouvez les utiliser pour vérifier que vos composants parlent comme vous le souhaitez.


1

- Mes réponses sont spécifiques à MS / Visual Studio -

Comment déterminez-vous ce qui peut être rendu simultané par rapport à ce qui doit être séquentiel?

Cela va prendre connaissance du domaine, il ne va pas y avoir de déclaration générale pour couvrir cela.

Comment reproduire des conditions d'erreur et voir ce qui se passe pendant l'exécution de l'application?

Beaucoup de journalisation, être capable d'activer / désactiver / relancer la journalisation dans les applications de production afin de la capturer dans la production. VS2010 Intellitrace est censé pouvoir vous aider, mais je ne l'ai pas encore utilisé.

Comment visualisez-vous les interactions entre les différentes parties concourantes de l'application?

Je n'ai pas de bonne réponse à cela, j'aimerais bien en voir une.


La journalisation va changer la façon dont le code s’exécute et peut donc conduire à l’erreur que vous avez après ne pas apparaître.
Matthew Lu

1

Je ne suis pas d'accord avec votre affirmation selon laquelle C n'est pas conçu pour la concurrence. C est conçu pour la programmation générale de systèmes et jouit d’une ténacité à indiquer les décisions critiques à prendre, et continuera de le faire pour les années à venir. Cela est vrai même lorsque la meilleure décision peut être de ne pas utiliser C. De plus, la concurrence d'accès en C est aussi difficile que votre conception est complexe.

J'essaie, dans la mesure de mes moyens, de mettre en oeuvre des verrous avec l'idée que, finalement, une programmation réellement pratique et sans verrous pourrait devenir une réalité pour moi. Par verrouillage, je ne parle pas d'exclusion mutuelle, je veux simplement dire d'un processus qui implémente une concurrence sécurisée sans recourir à l'arbitrage. Par pratique, je veux dire quelque chose qui est plus facile à porter qu’à implémenter. J'ai aussi très peu de formation formelle en informatique, mais je suppose que je suis autorisé à le souhaiter :)

Suite à cela, la plupart des insectes que je rencontre deviennent relativement peu profonds, ou si complètement ahurissants que je me retire dans un pub. Le pub devient une option attrayante uniquement lorsque le profilage d'un programme le ralentit suffisamment pour exposer des races supplémentaires qui ne sont pas liées à ce que j'essaie de trouver.

Comme d'autres l'ont souligné, le problème que vous décrivez est extrêmement spécifique à un domaine. J'essaie simplement, du mieux que je peux, d'éviter tout cas qui pourrait nécessiter un arbitrage (en dehors de mon processus) dans la mesure du possible. Si cela semble être une douleur royale, je réévalue la possibilité de donner à plusieurs threads ou processus un accès simultané et non sérialisé à quelque chose.

Encore une fois, jetez 'distribué' dedans et l'arbitrage devient un must. Avez-vous un exemple spécifique?


Pour clarifier ma déclaration, C n'a pas été conçu spécifiquement pour et autour de la concurrence. Cela contraste avec les langages tels que Go, Erlang et Scala, qui ont été conçus explicitement avec l’accès simultané à l’esprit. Je n'avais pas l'intention de dire que vous ne pouvez pas concurrencer C.
Berin Loritsch

1

Comment reproduire des conditions d'erreur et voir ce qui se passe pendant l'exécution de l'application?

Comment visualisez-vous les interactions entre les différentes parties concourantes de l'application?

Sur la base de mon expérience, la réponse à ces deux aspects est la suivante:

Traçage distribué

Le traçage distribué est une technologie qui capture les données de synchronisation de chaque composant simultané de votre système et vous les présente sous forme graphique. Les représentations des exécutions simultanées sont toujours entrelacées, ce qui vous permet de voir ce qui fonctionne en parallèle et ce qui ne l'est pas.

Le traçage distribué doit ses origines (bien sûr) aux systèmes distribués, qui sont par définition asynchrones et hautement concurrents. Un système distribué avec traçage distribué permet aux utilisateurs de:

a) identifier les goulots d'étranglement importants, b) obtenir une représentation visuelle des «exécutions» idéales de votre application, et c) fournir une visibilité sur le comportement simultané exécuté, d) obtenir des données temporelles pouvant être utilisées pour évaluer les différences entre les changements de votre système (extrêmement important si vous avez de forts accords de niveau de service).

Les conséquences du traçage distribué sont toutefois les suivantes:

  1. Il ajoute une surcharge à tous vos processus simultanés, car il se traduit par davantage de code à exécuter et à soumettre éventuellement sur un réseau. Dans certains cas, cette surcharge est très importante - même Google utilise son système de traçage Dapper uniquement sur un petit sous-ensemble de toutes les demandes afin de ne pas gâcher l'expérience utilisateur.

  2. Il existe de nombreux outils différents, qui ne sont pas tous interopérables les uns avec les autres. Ceci est quelque peu amélioré par des normes comme OpenTracing, mais pas complètement résolu.

  3. Il ne vous dit rien sur les ressources partagées et leur statut actuel. Vous pourrez peut-être deviner, en fonction du code de l'application et de ce que le graphique que vous voyez est en train de vous montrer, mais ce n'est pas un outil utile à cet égard.

  4. Les outils actuels supposent que vous avez de la mémoire et du stockage à revendre. L'hébergement d'un serveur timeseries peut ne pas être bon marché, en fonction de vos contraintes.

Logiciel de suivi des erreurs

Je me connecte à Sentry ci-dessus principalement parce que c'est l'outil le plus utilisé et pour une bonne raison - un logiciel de suivi des erreurs comme l'exécution du détournement de Sentry permet de transmettre simultanément une trace de pile des erreurs rencontrées à un serveur central.

L’avantage net de ce logiciel dédié en code concurrent:

  1. Les erreurs de duplication ne sont pas dupliquées . En d'autres termes, si un ou plusieurs systèmes concurrents rencontrent la même exception, Sentry incrémentera un rapport d'incident, mais n'enverra pas deux copies de l'incident.

Cela signifie que vous pouvez déterminer quel système simultané rencontre quel type d'erreur sans avoir à passer par d'innombrables rapports d'erreur simultanés. Si vous avez déjà été victime de spam par courrier électronique à partir d'un système distribué, vous savez à quoi ressemble un enfer.

Vous pouvez même "baliser" différents aspects de votre système concurrent (bien que cela suppose que votre travail ne soit pas entrelacé sur un seul thread, ce qui techniquement n'est pas simultané puisque le thread saute simplement d'une tâche à une autre, mais doit toujours traiter les gestionnaires d'événements à compléter) et voir une ventilation des erreurs par tag.

  1. Vous pouvez modifier ce logiciel de gestion des erreurs pour fournir des détails supplémentaires avec vos exceptions d'exécution. Quelles ressources ouvertes le processus avait-il? Y a-t-il une ressource partagée que ce processus tenait? Quel utilisateur a rencontré ce problème?

Ceci, en plus des traces de pile méticuleuses (et des mappes de sources, si vous devez fournir une version réduite de vos fichiers), facilite la détermination de ce qui ne va pas une grande partie du temps.

  1. (Spécifique à Sentry) Vous pouvez disposer d'un tableau de bord de rapports Sentry distinct pour les tests du système, ce qui vous permet de détecter les erreurs lors des tests.

Les inconvénients d'un tel logiciel incluent:

  1. Comme tout, ils ajoutent du volume. Vous ne voudrez peut-être pas un tel système sur du matériel embarqué, par exemple. Je recommande fortement de faire un essai de ce logiciel, en comparant une exécution simple avec et sans échantillonnage sur quelques centaines d'essais sur une machine inactive.

  2. Toutes les langues ne sont pas prises en charge de la même manière, car bon nombre de ces systèmes reposent sur la capture implicite d'une exception et toutes les langues ne disposent pas d'exceptions robustes. Cela étant dit, il existe des clients pour de nombreux systèmes.

  3. Ils peuvent constituer un risque pour la sécurité, car bon nombre de ces systèmes sont essentiellement à source fermée. Dans ce cas, faites preuve de la diligence requise pour les rechercher ou, si vous préférez, lancez les vôtres.

  4. Ils pourraient ne pas toujours vous donner les informations dont vous avez besoin. C'est un risque avec toutes les tentatives pour ajouter de la visibilité.

  5. La plupart de ces services ont été conçus pour des applications Web hautement concurrentes. Tous les outils ne sont donc peut-être pas parfaitement adaptés à votre cas d'utilisation.

En résumé : la visibilité est la partie la plus cruciale de tout système concurrent. Les deux méthodes que je décris ci-dessus, associées à des tableaux de bord dédiés sur le matériel et les données pour obtenir une image globale du système à tout moment, sont largement utilisées dans l’industrie, précisément pour remédier à ce problème.

Quelques suggestions supplémentaires

J'ai passé plus de temps que prévu à réparer des codes chez des personnes qui ont essayé de résoudre des problèmes concurrents de manière terrible. À chaque fois, j'ai trouvé des cas où les choses suivantes pourraient grandement améliorer l'expérience des développeurs (ce qui est tout aussi important que l'expérience des utilisateurs):

  • Comptez sur les types . La saisie permet de valider votre code et peut être utilisée au moment de l'exécution en tant que protection supplémentaire. Là où la saisie n'existe pas, utilisez des assertions et un gestionnaire d’erreur approprié pour intercepter les erreurs. Le code simultané nécessite un code défensif, et les types constituent le meilleur type de validation disponible.

    • Testez les liens entre les composants de code , pas seulement le composant lui-même. Ne confondez pas cela avec un test d'intégration complet - qui teste chaque lien entre chaque composant, et même dans ce cas, il recherche uniquement une validation globale de l'état final. C'est un moyen terrible d'attraper des erreurs.

Un bon test de liaison vérifie si, lorsqu'un composant parle à un autre composant de manière isolée , le message reçu et le message envoyé sont les mêmes que ceux attendus. Si vous avez deux composants ou plus qui dépendent d'un service partagé pour communiquer, mettez-les en synergie, demandez-leur d'échanger des messages via le service central et voyez s'ils obtiennent tous ce que vous attendez.

Découper des tests impliquant beaucoup de composants en un test des composants eux-mêmes et un test de la communication de chacun des composants vous donne une confiance accrue dans la validité de votre code. Un ensemble de tests aussi rigoureux vous permet de faire respecter les contrats entre les services et de détecter les erreurs inattendues qui se produisent lorsqu'ils sont exécutés simultanément.

  • Utilisez les bons algorithmes pour valider l'état de votre application. Je parle de choses simples, comme par exemple lorsqu'un processus maître attend que tous ses employés terminent une tâche et ne souhaitent passer à l'étape suivante que si tous les employés ont terminé leur travail - il s'agit d'un exemple de détection globale. terminaison, pour lesquels il existe des méthodologies connues telles que l'algorithme de Safra.

Certains de ces outils sont livrés avec des langages - Rust, par exemple, garantit que votre code n’aura pas de situation de concurrence critique au moment de la compilation, tandis que Go propose un détecteur de blocage intégré qui fonctionne également au moment de la compilation. Si vous pouvez résoudre les problèmes avant qu'ils n'atteignent la production, c'est toujours une victoire.

Une règle générale: conception en cas d'échec dans les systèmes concurrents . Anticipez que les services communs vont planter ou casser. Cela vaut même pour le code qui n'est pas distribué sur plusieurs ordinateurs: le code simultané sur un seul ordinateur peut s'appuyer sur des dépendances externes (telles qu'un fichier journal partagé, un serveur Redis, un putain de serveur MySQL) qui peuvent disparaître ou être supprimées à tout moment. .

Pour ce faire, la meilleure solution consiste à valider de temps à autre l'état de l'application - effectuez des contrôles de l'état de santé de chaque service et assurez-vous que les clients de ce service sont informés de leur mauvais état de santé. Les outils de conteneur modernes tels que Docker le font très bien et devraient être utilisés pour le traitement en bac à sable.

Comment déterminez-vous ce qui peut être rendu simultané et séquentiel?

L'une des principales leçons que j'ai apprises en travaillant sur un système hautement concurrentiel est la suivante: vous ne pouvez jamais avoir assez de métriques . Les métriques doivent contrôler absolument tout dans votre application - vous n'êtes pas un ingénieur si vous ne mesurez pas tout.

Sans métriques, vous ne pouvez pas faire quelques choses très importantes:

  1. Évaluez la différence apportée par les modifications apportées au système. Si vous ne savez pas si le bouton de réglage A fait monter la métrique B et la métrique C, vous ne savez pas comment réparer votre système lorsque des personnes poussent du code inopinément malin sur votre système (et le feront ensuite sur votre système) .

  2. Comprenez ce que vous devez faire ensuite pour améliorer les choses. Jusqu'à ce que vous sachiez que les applications manquent de mémoire, vous ne pouvez pas déterminer si vous devez obtenir plus de mémoire ou acheter plus de disque pour vos serveurs.

Les métriques sont tellement cruciales et essentielles que j'ai fait un effort conscient pour planifier ce que je veux mesurer avant même de penser à ce qu'un système nécessitera. En fait, les métriques sont tellement cruciales que je crois qu’elles sont la bonne réponse à cette question: vous ne savez que ce qui peut être fait de manière séquentielle ou simultanée lorsque vous mesurez ce que font les bits de votre programme. Une conception appropriée utilise des chiffres et non des conjectures.

Cela étant dit, il existe certainement quelques règles de base:

  1. Séquentielle implique la dépendance. Deux processus doivent être séquentiels si l’un dépend de l’autre. Les processus sans dépendance doivent être simultanés. Cependant, prévoyez un moyen de gérer les échecs en amont qui n'empêche pas les processus en aval d'attendre indéfiniment.

  2. Ne mélangez jamais une tâche liée aux E / S avec une tâche liée au processeur sur le même noyau. N'écrivez pas (par exemple) un robot d'exploration Web qui lance dix demandes simultanées dans le même fil, les récupère dès qu'elles arrivent et s'attend à passer à cinq cents - les demandes d'E / S sont placées dans une file d'attente en parallèle, mais la CPU les passera toujours en série. (Ce modèle événementiel mono-threadé est populaire, mais il est limité à cause de cet aspect - plutôt que de comprendre cela, les gens se tordent simplement la main et disent que Node n'échelle pas, pour vous donner un exemple).

Un seul thread peut faire beaucoup de travail d'E / S. Mais pour utiliser pleinement la simultanéité de votre matériel, utilisez des pools de threads qui occupent tous les cœurs. Dans l'exemple ci-dessus, le lancement de cinq processus Python (chacun pouvant utiliser un cœur sur une machine à six cœurs) juste pour le travail du processeur et un sixième thread Python juste pour le travail d'E / S évolueront beaucoup plus rapidement que vous ne le pensez.

La seule façon de tirer parti de la concurrence du processeur consiste à utiliser un pool de threads dédié. Un seul thread est souvent suffisant pour de nombreux travaux liés aux E / S. C’est pourquoi les serveurs Web événementiels tels que Nginx s’adaptent mieux (qu’ils effectuent un travail purement lié aux E / S) et Apache (qui confond le travail lié aux E / S avec quelque chose nécessitant un processeur et lance un processus par requête), mais pourquoi utiliser Node pour les exécuter des dizaines de milliers de calculs GPU reçus en parallèle est une idée terrible .


0

Eh bien, pour le processus de vérification, lors de la conception d’un grand système concurrent, j’ai tendance à tester le modèle à l’aide de LTSA - Labeled Transition System Analyzer . Il a été développé par mon ancien tuteur, qui est un vétéran dans le domaine de la concurrence et qui est actuellement responsable de l'informatique à l'Impériale.

En ce qui concerne ce qui peut et ne peut pas être simultané, il existe des analyseurs statiques qui pourraient le montrer, bien que j'aie tendance à ne dessiner que des diagrammes de planification pour les sections critiques, comme vous le feriez pour la gestion de projet. Identifiez ensuite les sections qui effectuent la même opération de manière répétée. Une solution rapide consiste simplement à trouver des boucles, car ce sont elles qui bénéficient généralement du traitement en parallèle.


0

Comment déterminez-vous ce qui peut être rendu simultané par rapport à ce qui doit être séquentiel?

Pratiquement tout ce que vous écrivez peut tirer parti de la concurrence, en particulier du cas d'utilisation "diviser une conquête". Une meilleure question est ce qui devrait être concurrente?

Le filetage en C # de Joseph Albahari énumère cinq utilisations courantes.

Le multithreading a de nombreuses utilisations; voici les plus courantes:

Maintenir une interface utilisateur réactive

En exécutant des tâches fastidieuses sur un thread «travailleur» parallèle, le thread d'interface utilisateur principal est libre de poursuivre le traitement des événements de clavier et de souris.

Utilisation efficace d'un processeur par ailleurs bloqué

Le multithreading est utile lorsqu'un thread attend une réponse d'un autre ordinateur ou d'un autre matériel. Lorsqu'un thread est bloqué lors de l'exécution de la tâche, d'autres threads peuvent tirer parti de l'ordinateur autrement déchargé.

Programmation parallèle

Le code qui effectue des calculs intensifs peut être exécuté plus rapidement sur des ordinateurs multicœurs ou multiprocesseurs si la charge de travail est partagée entre plusieurs threads dans une stratégie de «division et conquête» (voir la cinquième partie).

Exécution spéculative

Sur les machines multicœurs, vous pouvez parfois améliorer les performances en prévoyant quelque chose qui peut être nécessaire, puis en le faisant à l'avance. LINQPad utilise cette technique pour accélérer la création de nouvelles requêtes. Une variante consiste à exécuter en parallèle plusieurs algorithmes différents qui résolvent tous la même tâche. Quel que soit le joueur qui termine le premier "gagne", cela est efficace lorsque vous ne pouvez pas savoir à l'avance quel algorithme sera exécuté le plus rapidement.

Permettre le traitement simultané des demandes

Sur un serveur, les demandes client peuvent arriver simultanément et doivent donc être traitées en parallèle (.NET Framework crée automatiquement des threads pour cela si vous utilisez ASP.NET, WCF, Services Web ou Remoting). Cela peut également être utile sur un client (par exemple, gérer un réseau entre homologues, voire plusieurs requêtes de l'utilisateur).

Si vous n'essayez pas de faire l'une des choses ci-dessus, vous feriez probablement mieux d'y réfléchir sérieusement.

Comment reproduire des conditions d'erreur et voir ce qui se passe pendant l'exécution de l'application?

Si vous utilisez .NET et que vous avez écrit des cas d'utilisation, vous pouvez utiliser CHESS, qui permet de recréer des conditions d'entrelacement de threads spécifiques, ce qui vous permet de tester votre correctif.

Comment visualisez-vous les interactions entre les différentes parties concourantes de l'application?

Ça dépend du contexte. Pour les scénarios de travailleur, je pense à un gestionnaire subordonné. Le gestionnaire demande au subordonné de faire quelque chose et attend les mises à jour de statut.

Pour des tâches concurrentes non liées, je pense aux ascenseurs ou aux voitures dans des voies de circulation séparées.

Pour la synchronisation, je pense parfois aux feux de circulation ou aux styles de virage.

De même, si vous utilisez C # 4.0, vous voudrez peut-être jeter un coup d’œil à la bibliothèque de tâches parallèle.


0

Ma réponse à cette question est:

  • Comment déterminez-vous ce qui peut être rendu simultané par rapport à ce qui doit être séquentiel?

Tout d’abord, j’ai besoin de savoir pourquoi je devrais utiliser la concurrence, car j’ai découvert que les gens sont excités par l’idée qui sous-tend la concurrence, mais ne pensent pas toujours au problème qu’ils essaient de résoudre.

Si vous devez simuler une situation réelle, comme des files d'attente, des flux de travail, etc., vous devrez probablement utiliser une approche simultanée.

Maintenant que je sais que je devrais l'utiliser, il est temps d'analyser le compromis, si vous avez beaucoup de processus, vous pouvez penser à un temps système de communication, mais si vous devez créer de nouvelles, vous risquez de vous retrouver sans solution concurrente (problème de réanimation si alors.)

  • Comment reproduire des conditions d'erreur et voir ce qui se passe pendant l'exécution de l'application?

Je ne suis pas un expert en la matière, mais je pense que pour les systèmes concurrents, ce n'est pas la bonne approche. Une approche théorique doit être choisie, en recherchant les 4 impératifs de blocage dans des domaines critiques:

  1. Non préemptivité
  2. Tiens et attends
  3. Exclusion motuelle
  4. Chaîne circulaire

    • Comment visualisez-vous les interactions entre les différentes parties concourantes de l'application?

J'essaie d'abord d'identifier qui sont les participants aux interactions, puis comment communiquent-ils et avec qui. Enfin, les graphiques et les diagrammes d'interaction m'aident à visualiser. Mon bon vieux tableau blanc ne peut être battu par aucun autre type de média.


0

Je serai franc. J'adore les outils. J'utilise beaucoup d'outils. Ma première étape consiste à tracer les chemins prévus pour le flux d’état. Ma prochaine étape consiste à essayer de déterminer si cela en vaut la peine ou si le flux d'informations requis rendra le code en série trop souvent. Ensuite, je vais essayer de rédiger des modèles simples. Celles-ci peuvent aller d'une pile de sculptures de cure-dents brutes à des exemples simples similaires en python. Ensuite, je regarde quelques-uns de mes livres préférés, comme le petit livre de sémaphores, et vois si quelqu'un a déjà trouvé une meilleure solution à mon problème.

Puis je commence à coder.
Je rigole. Un peu plus de recherche d'abord. J'aime m'asseoir avec un autre pirate informatique et parcourir l'exécution attendue du programme à un niveau élevé. Si des questions se posent, nous passons à un niveau inférieur. Il est important de savoir si quelqu'un d'autre comprend suffisamment votre solution pour la maintenir.

Enfin, je commence à coder. J'essaie de garder les choses très simples en premier. Juste le chemin du code, rien d'extraordinaire. Déplacer aussi peu que possible. Évitez les écritures. Évitez les lectures susceptibles d'entrer en conflit avec les écritures. Evitez surtout les écritures en conflit avec les écritures. Il est très facile de constater que vous en avez un nombre positivement toxique et que votre belle solution n’est soudainement qu’un petit peu plus qu’une approche en série effrénée en cache.

Une bonne règle consiste à utiliser des cadres partout où vous le pouvez. Si vous écrivez vous-même des composants de threads de base, tels que de bonnes structures de données synchronisées, ou des primitives synchro véritables, il est presque certain que vous allez vous faire sauter la jambe.

Enfin, des outils. Le débogage est très difficile. J'utilise valgrind \ callgrind sur Linux en conjonction avec PIN et sur des studios parallèles sous Windows. N'essayez pas de déboguer ce matériel à la main. Vous pouvez probablement. Mais vous souhaiterez probablement ne pas l'avoir fait. Dix heures à maîtriser des outils puissants et quelques bons modèles vous feront gagner des centaines d’heures plus tard.

Avant tout, travaillez progressivement. Travailler avec soin. N'écrivez pas de code simultané lorsque vous êtes fatigué. Ne l'écrivez pas si vous avez faim. En fait, si vous pouvez l'éviter, ne l'écrivez tout simplement pas. La concurrence est difficile, et j’ai constaté que de nombreuses applications l’énumérant en tant que fonctionnalité sont souvent livrées avec.

En résumé:
Début:
Think
Talk
Test
Écrire simplement
Lire
Test
Ecrire
Debug
GOTO Begin

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.