Existe-t-il des scénarios dans lesquels interroger des événements serait préférable à l'utilisation du modèle d'observateur ? J'ai peur d'utiliser les sondages et je ne commencerais à les utiliser que si quelqu'un me donnait un bon scénario. Tout ce à quoi je peux penser, c'est comment le modèle d'observateur est meilleur que le sondage. Considérez ce scénario:
Vous programmez un simulateur de voiture. La voiture est un objet. Dès que la voiture s'allume, vous voulez jouer un extrait sonore "vroom vroom".
Vous pouvez modéliser ceci de deux manières:
Polling : interroge l'objet car toutes les secondes pour voir s'il est activé. Quand il est allumé, jouez le clip audio.
Modèle d'observateur : Faites de la voiture le sujet du modèle d'observateur. Faites-le publier l'événement "on" à tous les observateurs quand il s'allume. Créez un nouvel objet sonore qui écoute la voiture. Faites-le implémenter le rappel "on", qui lit le clip audio.
Dans ce cas, je pense que le motif observateur gagne. Premièrement, l'interrogation utilise davantage de processeur. Deuxièmement, le clip sonore ne se déclenche pas immédiatement lorsque la voiture s'allume. Il peut y avoir un intervalle pouvant aller jusqu'à 1 seconde en raison de la période de scrutin.
la source
Réponses:
Imaginez que vous souhaitiez être averti à chaque cycle du moteur, par exemple pour afficher une mesure du régime du conducteur.
Modèle d'observateur: le moteur publie un événement "cycle moteur" à tous les observateurs pour chaque cycle. Créez un écouteur qui compte les événements et met à jour l'affichage RPM.
Interrogation: l' affichage du régime demande au moteur un compteur de cycles à intervalles réguliers et met à jour l'affichage du régime en conséquence.
Dans ce cas, le modèle d'observateur serait probablement perdu: le cycle du moteur est un processus à haute fréquence et haute priorité, vous ne voulez pas retarder ou bloquer ce processus juste pour mettre à jour un affichage. Vous ne souhaitez pas non plus écraser le pool de threads avec des événements de cycle de moteur.
PS: J'utilise aussi fréquemment le mode d'interrogation dans la programmation distribuée:
Modèle d'observateur: le processus A envoie au processus B un message indiquant "chaque fois qu'un événement E se produit, envoie un message au processus A".
Mode d’ interrogation : Le processus A envoie régulièrement au processus B un message indiquant "Si votre événement E s’est produit depuis la dernière interrogation, envoyez-moi un message maintenant".
Le modèle d'interrogation produit un peu plus de charge réseau. Mais le modèle d'observateur a aussi des inconvénients:
la source
La scrutation est préférable si le processus de scrutation est beaucoup plus lent que les choses qu'il scrute. Si vous écrivez des événements dans une base de données, il est souvent préférable d'interroger tous vos producteurs d'événements, de collecter tous les événements survenus depuis le dernier interrogation, puis de les écrire en une seule transaction. Si vous tentiez d'écrire chaque événement au fur et à mesure qu'il se produisait, vous ne pourriez peut-être pas suivre le rythme et finiriez par avoir des problèmes lorsque vos files d'attente de saisie s'épuisent. Cela a également plus de sens dans les systèmes distribués à couplage lâche, où la latence est élevée ou où l'établissement et le démontage de la connexion sont coûteux. Je trouve les systèmes de sondage plus faciles à écrire et à comprendre, mais dans la plupart des situations, les observateurs ou les consommateurs événementiels semblent offrir de meilleures performances (selon mon expérience).
la source
Il est beaucoup plus facile d' interroger un réseau lorsque les connexions peuvent échouer, les serveurs peuvent être utilisés, etc. Rappelez-vous qu'à la fin de la journée, un socket TCP doit «interroger» les messages Keep-a-Live, sinon le serveur assumera le contrôle du client. est parti.
L'interrogation est également utile lorsque vous souhaitez conserver une interface utilisateur mise à jour, mais les objets sous-jacents évoluant très rapidement , il est inutile de mettre à jour l'interface utilisateur plus de quelques fois par seconde dans la plupart des applications.
Si le serveur peut répondre «pas de changement» à un coût très bas, si vous n'interrogez pas trop souvent et que vous n'avez pas 1000 interrogations, l'interrogation fonctionne très bien dans la vie réelle.
Cependant, pour les cas « en mémoire », j’utilise par défaut le motif observateur car c’est normalement le moins de travail.
la source
Les sondages ont des inconvénients, vous les avez déjà énoncés dans votre question.
Cependant, cela peut être une meilleure solution lorsque vous voulez vraiment dissocier l'observable de l'observateur. Mais parfois, il peut être préférable d’utiliser un emballage observable pour que l’objet soit observé dans de tels cas.
Je n'utilisais l'interrogation que lorsque l'observable ne pouvait pas être observé avec les interactions d'objet, ce qui est souvent le cas lors de l'interrogation de bases de données, par exemple, pour lesquelles il est impossible d'avoir des rappels. Un autre problème peut être le multithreading, où il est souvent plus sûr d'interroger et de traiter les messages plutôt que d'appeler directement des objets, afin d'éviter les problèmes de simultanéité.
la source
Pour un bon exemple de ce qui se passe lorsque l'interrogation prend le relais de la notification, examinez les piles de réseaux du système d'exploitation.
C'était un gros problème pour Linux lorsque la pile de mise en réseau a activé NAPI, une API de mise en réseau permettant aux pilotes de passer d'un mode d'interruption (notification) à un mode d'interrogation.
Avec plusieurs interfaces Ethernet gigabit, les interruptions surchargeraient souvent le processeur, ce qui ralentirait le système. Lors de l'interrogation, les cartes réseau collectent les paquets dans des mémoires tampons jusqu'à leur interrogation ou les cartes écrivent même les paquets en mémoire via DMA. Ensuite, lorsque le système d'exploitation est prêt, il interroge la carte pour toutes ses données et effectue le traitement TCP / IP standard.
Le mode d'interrogation permet à la CPU de collecter des données Ethernet à son taux de traitement maximum sans charge d'interruption inutile. Le mode d'interruption permet à la CPU de rester inactive entre les paquets lorsque le travail n'est pas trop occupé.
Le secret est de savoir quand passer d’un mode à l’autre. Chaque mode présente des avantages et doit être utilisé au bon endroit.
la source
J'adore voter! Est ce que je? Oui! Est ce que je? Oui! Est ce que je? Oui! Est-ce que je reste? Oui! Et maintenant? Oui!
Comme d'autres l'ont mentionné, il peut s'avérer extrêmement inefficace de procéder à une interrogation uniquement pour retrouver le même état inchangé à maintes reprises. Telle est la recette pour brûler les cycles du processeur et réduire considérablement la durée de vie de la batterie des appareils mobiles. Bien sûr, il n’est pas inutile de retrouver à chaque fois un état nouveau et significatif à un rythme pas plus rapide que souhaité.
Mais la raison principale pour laquelle j'aime les sondages est sa simplicité et sa nature prévisible. Vous pouvez suivre le code et voir facilement quand et où les choses vont se passer et dans quel fil. Si, en théorie, nous vivions dans un monde où les sondages constituaient un gaspillage négligeable (bien que la réalité soit loin de là), alors je pense que cela simplifierait considérablement le maintien du code. Et c’est l’avantage de procéder à des sondages et à des levées car je vois si nous pourrions faire abstraction des performances, même si nous ne le devrions pas dans ce cas.
Quand j'ai commencé à programmer à l'époque du DOS, mes petits jeux tournaient autour du sondage. J'ai copié du code d'assemblage d'un livre que je comprenais à peine en ce qui concerne les interruptions de clavier et je lui ai fait stocker un tampon d'états de clavier, point auquel ma boucle principale était toujours en scrutation. La touche haut est-elle enfoncée? Nan. La touche haut est-elle enfoncée? Nan. Et maintenant? Nan. À présent? Oui. Ok, déplace le joueur.
Et bien qu’incroyablement inutile, j’ai trouvé qu’il était tellement plus facile de raisonner par rapport à ces jours de programmation multitâche et événementielle. Je savais exactement quand et où les choses se produiraient à tout moment et il était plus facile de garder des taux de trame stables et prévisibles sans problème.
Ainsi, depuis lors, j'ai toujours essayé de trouver un moyen de tirer parti des avantages et de la prévisibilité de cette opération sans brûler réellement les cycles du processeur, par exemple en utilisant des variables de condition pour informer les threads de se réveiller à quel moment ils peuvent extraire le nouvel état, faire leur truc, et se rendormir en attendant d'être averti à nouveau.
Et d’une manière ou d’une autre, je trouve que les files d’événements sont beaucoup plus faciles à utiliser, du moins que les modèles d’observateurs, même s’ils ne facilitent pas encore autant la tâche de prédire où vous allez finir ou ce qui va se passer. Ils centralisent au moins le flux de contrôle de gestion des événements sur quelques zones clés du système et traitent toujours ces événements dans le même thread au lieu de rebondir d'une fonction à un endroit complètement distant et inattendu tout à coup en dehors d'un thread de gestion d'événements central. Donc, la dichotomie ne doit pas toujours être entre observateurs et sondages. Les files d’événements sont en quelque sorte un terrain d’entente.
Mais oui, d’une manière ou d’une autre, j’ai tellement de facilité à raisonner au sujet de systèmes qui font des choses qui sont analogiquement plus proches du type de flux de contrôle prévisibles que j’avais quand j’interrogeais il ya très longtemps, tout en contrant la tendance du travail à se produire moments où aucun changement d'état n'a eu lieu. Il y a donc cet avantage si vous pouvez le faire d'une manière qui ne brûle pas inutilement les cycles du processeur, contrairement aux variables de condition.
Boucles homogènes
D'accord, j'ai eu un excellent commentaire
Josh Caswell
qui a mis en évidence certaines maladresses dans ma réponse:Techniquement, la variable condition elle-même applique le motif de l'observateur pour réveiller / notifier les threads. Par conséquent, appeler cette "interrogation" serait probablement extrêmement trompeur. Mais je trouve que cela procure un avantage similaire à celui obtenu lors de l'interrogation à partir des jours DOS (juste en termes de flux de contrôle et de prévisibilité). Je vais essayer de l'expliquer mieux.
Ce que j’ai trouvé attrayant à l’époque, c’était que vous pouviez consulter une section de code ou y faire un suivi et dire: «Très bien, toute cette section est dédiée à la gestion des événements de clavier. Rien d’autre ne se passera dans cette section de code. Et je sais exactement ce qui va se passer avant, et je sais exactement ce qui va se passer après (physique et rendu, par exemple). " L'interrogation des états du clavier vous a donné ce type de centralisation du flux de contrôle en ce qui concerne le traitement de ce qui devrait se passer en réponse à cet événement externe. Nous n'avons pas réagi à cet événement externe immédiatement. Nous avons répondu à notre convenance.
Lorsque nous utilisons un système Push basé sur un modèle Observer, nous perdons souvent ces avantages. Un contrôle peut être redimensionné, ce qui déclenche un événement de redimensionnement. Lorsque nous traçons à travers, nous nous trouvons dans un contrôle exotique qui effectue beaucoup de choses personnalisées lors de son redimensionnement, ce qui déclenche plus d'événements. Nous finissons par être complètement surpris de retrouver tous ces événements en cascade pour savoir où nous nous retrouvons dans le système. En outre, nous pourrions constater que tout cela ne se produit même pas systématiquement dans un thread donné, car le thread A pourrait redimensionner un contrôle ici alors que le thread B redimensionnait également un contrôle ultérieurement. J'ai donc toujours trouvé cela très difficile à raisonner étant donné combien il est difficile de prédire où tout se passe et ce qui va se passer.
La file d’événements est pour moi un peu plus simple à raisonner car elle simplifie l’emplacement de toutes ces choses, au moins au niveau des threads. Cependant, beaucoup de choses disparates pourraient se produire. Une file d'attente d'événements peut contenir un mélange éclectique d'événements à traiter, et chacun d'entre eux peut encore nous surprendre quant à la cascade d'événements survenus, à l'ordre dans lequel ils ont été traités et à la manière dont nous finissons par rebondir dans la base de code. .
Ce que je considère comme "proche" de l'interrogation ne devrait pas utiliser une file d'attente d'événements mais différer un type de traitement très homogène. A l'
PaintSystem
aide d'une variable de condition, il peut être alerté que des travaux de peinture sont nécessaires pour repeindre certaines cellules de la grille d'une fenêtre. À ce stade, il effectue une simple boucle séquentielle entre les cellules et repeint tout ce qui s'y trouve dans le bon ordre de z. Il se peut qu’il existe un niveau d’appel indirection / dispatch dynamique pour déclencher les événements de peinture de chaque widget résidant dans une cellule à repeindre, mais c’est tout - une couche d’appels indirects. La variable condition utilise le modèle observateur pour alerterPaintSystem
qu’elle a du travail à faire, mais elle ne spécifie rien de plus que cela, et le paramètrePaintSystem
est consacré à une tâche uniforme et très homogène à ce stade. Lorsque nous déboguons et suivons dans lePaintSystem's
code, nous savons qu’il ne se passera rien d’autre que de peindre.Il s’agit donc essentiellement de faire en sorte que ces systèmes effectuent des boucles homogènes sur des données en appliquant une responsabilité très singulière, au lieu de boucles non homogènes sur des types de données disparates et en assumant de nombreuses responsabilités, comme nous pourrions l’obtenir avec le traitement des files d’événements.
Nous visons ce genre de chose:
Par opposition à:
Et ainsi de suite. Et il ne doit pas nécessairement y avoir un fil par tâche. Un thread peut appliquer une logique de mise en page (redimensionnement / repositionnement) aux contrôles de l'interface graphique et les repeindre, mais il peut ne pas gérer les clics du clavier ou de la souris. Vous pouvez donc considérer cela comme une simple amélioration de l'homogénéité d'une file d'attente d'événements. Mais nous n’avons pas non plus besoin d’utiliser une file d’événements et d’entrelacer les fonctions de redimensionnement et de dessin. Nous pouvons faire comme:
Ainsi, l'approche ci-dessus utilise simplement une variable de condition pour informer le fil quand il y a du travail à faire, mais elle n'entrelace pas différents types d'événements (redimensionner dans une boucle, peindre dans une autre boucle, pas un mélange des deux) et cela ne se produit pas. pas la peine de communiquer quel est exactement le travail à faire (le fil de discussion "découvre" qu’au réveil, en regardant les états du système à l’échelle du système). Chaque boucle qu’elle effectue est alors de nature très homogène, ce qui permet de raisonner facilement sur l’ordre dans lequel tout se passe.
Je ne sais pas comment appeler ce type d'approche. Je n'ai pas vu d'autres moteurs d'interface graphique faire cela et c'est un peu ma propre approche exotique à la mienne. Mais avant d’essayer d’implémenter des frameworks d’interface graphique multithread à l’aide d’observateurs ou de files d’événements, j’avais énormément de difficulté à le déboguer et j’ai rencontré des conditions de course obscures et des blocages que je n’étais pas assez malin pour régler de manière à me mettre en confiance. à propos de la solution (certaines personnes pourraient le faire mais je ne suis pas assez intelligent). Ma conception de la première itération consistait simplement à appeler une fente directement par le biais d'un signal. Certaines fentes généraient alors d'autres threads pour effectuer un travail asynchrone. C'était le plus difficile à raisonner et je trébuchais sur les conditions de course et les blocages. La deuxième itération utilisait une file d’événements et c’était un peu plus facile à raisonner, mais ce n’est pas assez facile pour mon cerveau de le faire sans me heurter à l’impasse obscure et aux conditions de la course. La troisième et dernière itération a utilisé l'approche décrite ci-dessus, et finalement cela m'a permis de créer un cadre d'interface graphique multithread que même un simple imbécile comme moi pourrait implémenter correctement.
Ensuite, ce type de conception d’interface utilisateur multithread finale m’a permis de proposer autre chose qui était tellement plus facile à raisonner et à éviter ce type d’erreurs que j’avais tendance à commettre, et l’une des raisons pour lesquelles j’ai trouvé si facile de raisonner à Le moins, c’est à cause de ces boucles homogènes et de leur ressemblance avec le flux de contrôle, comme lorsque j’interrogeais sous DOS (bien que ce ne soit pas vraiment une interrogation et que nous n’effectuions du travail que s’il y avait du travail à faire). L’idée était de s’éloigner autant que possible du modèle de gestion des événements, ce qui implique des boucles non homogènes, des effets secondaires non homogènes, des flux de contrôle non homogènes, et de travailler de plus en plus en faveur de boucles homogènes fonctionnant de manière uniforme sur des données homogènes et isolant les données. et en unifiant les effets secondaires de manière à ce qu'il soit plus facile de se concentrer sur "quoi"
la source
LayoutSystem
qui dort normalement, mais lorsque l'utilisateur redimensionne un contrôle, il utilise une variable de condition pour le réactiverLayoutSystem
. Ensuite, leLayoutSystem
redimensionne toutes les commandes nécessaires et se rendort. Au cours de ce processus, les régions rectangulaires dans lesquellesPaintSystem
se trouvent les widgets sont marquées comme nécessitant des mises à jour. Un point se réveille puis passe à travers ces régions rectangulaires, en repeignant celles qui doivent être redessinées dans une boucle séquentielle plate.Je vous donne un aperçu plus détaillé de la manière conceptuelle de penser le modèle d'observateur. Pensez à un scénario comme s’abonner à une chaîne youtube. Un grand nombre d'utilisateurs sont abonnés au canal et une fois qu'il y a une mise à jour sur le canal qui comprend de nombreuses vidéos, l'abonné est averti qu'il y a un changement dans ce canal particulier. Nous avons donc conclu que si channel est SUBJECT, il a la capacité de s'abonner, de se désabonner et d'avertir tous les OBSERVATEURS inscrits sur le canal.
la source