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:
"comme utiliser des variables de condition pour avertir les threads de se réveiller" Sonne comme un arrangement basé sur les événements / observateur
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 alerter PaintSystem
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 le PaintSystem'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:
when there's work to do:
for each thing:
apply a very specific and uniform operation to the thing
Par opposition à:
when one specific event happens:
do something with relevant thing
in relevant thing's event:
do some more things
in thing1's triggered by thing's event:
do some more things
in thing2's event triggerd by thing's event:
do some more things:
in thing3's event triggered by thing2's event:
do some more things
in thing4's event triggered by thing1's event:
cause a side effect which shouldn't be happening
in this order or from this thread.
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:
in thread dedicated to layout and painting:
when there's work to do:
for each widget that needs resizing/reposition:
resize/reposition thing to target size/position
mark appropriate grid cells as needing repainting
for each grid cell that needs repainting:
repaint cell
go back to sleep
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"