Principes et conseils :
|
Approche |
Avantages |
Inconvénients |
---|---|---|
Mono-processus, pas d'unité d'exécution |
|
|
Mono-processus, multi-unité d'exécution |
|
|
Multi-processus |
|
|
Une approche évolutive classique consiste à démarrer avec une architecture mono-processus, en ajoutant des processus pour les groupes de comportements qui doivent se produire simultanément. A l'intérieur de ces regroupements plus larges, envisagez des besoins supplémentaires en accès concurrents, en ajoutant des unités d'exécution dans les processus, afin d'augmenter l'accès concurrent.
Au départ, il convient d'attribuer beaucoup d'objets actifs à une seule tâche ou à une seule unité d'exécution du système d'exploitation, en utilisant le programmateur d'objets actifs spécialisé. De cette manière, il est habituellement possible de réaliser une simulation très simple des accès concurrents, bien qu'avec une seule tâche ou unité d'exécution du système d'exploitation, il ne sera pas possible de bénéficier de machines à UC multiples. La décision clé est d'isoler le comportement d'arrêt dans des unités d'exécution séparées, afin que ce comportement ne devienne pas un goulot d'étranglement. Cela va aboutir à une séparation des objets actifs ayant un comportement d'arrêt, au sein de leurs propres unités d'exécution du système d'exploitation.
Malheureusement, comme beaucoup de décisions architecturales, il n'existe aucune réponse facile : la bonne solution implique une approche soigneusement équilibrée. Des petits prototypes d'architecture peuvent être utilisés pour explorer les implications d'un ensemble de choix particulier. Dans la réalisation de prototypes de l'architecture des processus, concentrez-vous sur l'ajout de nouveaux processus jusqu'au nombre maximum théorique pour le système. Considérez les questions suivantes :
Les objets actifs peuvent communiquer les uns avec les autres en mode synchrone ou asynchrone. La communication synchrone est utile parce qu'elle simplifie les collaborations complexes, en utilisant des séquences contrôlées de manière stricte. C'est-à-dire que, pendant qu'un objet actif est en train de s'exécuter jusqu'à la fin, impliquant des appels synchrones de la part d'autres objets actifs, toute action concurrente initiée par d'autres objets peut être ignorée jusqu'à ce que la séquence complète soit terminée.
Alors que cela est utile dans certains cas, cela peut également être problématique, car il peut se produire qu'un événement plus important et de priorité élevée doive attendre (inversion des priorités). Ceci est exacerbé par la possibilité que l'objet appelé en mode synchrone puisse lui-même être bloqué, en attente d'une réponse à un appel synchrone qui lui est propre. Cela peut mener à une inversion illimitée des priorités. Dans le cas le plus extrême, s'il existe une circularité dans la chaîne d'appels synchrones, cela peut aboutir à un blocage total.
Les appels asynchrones évitent ce problème, en utilisant des temps de réponse limités. Cependant, suivant l'architecture logicielle, la communication asynchrone mène souvent à un code plus complexe, puisqu'un objet actif peut être amené à répondre à plusieurs événements asynchrones (dont chacun pourrait nécessiter une séquence complexe d'interactions avec les autres objets actifs) à tout moment. Cela peut être difficile et sujet à des erreurs au niveau de l'implémentation.
L'utilisation d'une technologie de messagerie asynchrone garantissant la communication des messages peut simplifier la tâche de programmation de l'application. L'application peut continuer, même si la connexion réseau ou l'application distante est indisponible. La messagerie asynchrone n'exclut pas son utilisation en mode synchrone. La technologie synchrone nécessitera une connexion, pour être disponible dès que l'application sera disponible. Du fait que l'on sait qu'une connexion existe, le traitement de la validation pourrait être plus facile.
Bien que la dépense liée au changement de contexte des objets actifs puisse être très basse, il se peut que certaines applications trouvent encore ce coût inacceptable. Cela se produit habituellement dans les situations où de grandes quantités de données doivent être traitées à un haut débit. Dans ce cas, nous devrons peut-être ré-utiliser des objets passifs et des techniques de gestion des accès concurrents plus traditionnelles (mais à plus haut risque), comme les sémaphores.
Ces considérations n'impliquent cependant pas que nous devions entièrement abandonner l'approche de l'objet actif. Même dans des applications qui traitent énormément de données, la partie sensible aux performances est souvent une partie relativement petite du système global. Cela implique que le reste du système peut encore bénéficier du paradigme de l'objet actif.
En général, la performance est seulement un des critères de conception, lorsqu'il est question de conception du système. Si le système est complexe, alors d'autres critères tels que la maintenabilité, la facilité de changement, le caractère compréhensible, etc. ont un niveau d'importance égal, voire supérieur. L'approche de l'objet actif présente un avantage certain, puisqu'il masque une grande partie de la complexité des accès concurrents et de leur gestion, tandis qu'il permet d'exprimer la conception dans des termes spécifiques aux applications, par opposition aux mécanismes de bas niveau, spécifiques à la technologie.
Les composants simultanés sans interaction sont quasiment sans importance. Presque tous les défis de conception ont un rapport avec les interactions parmi les activités concurrentes. Nous devons donc concentrer notre énergie sur la compréhension des interactions. Voici certaines questions à poser :
Une fois que l'interaction est comprise, nous pouvons envisager deux manières de l'implémenter. L'implémentation doit être sélectionnée pour offrir la meilleure conception, conforme aux objectifs de performance du système. Les conditions de performances incluent généralement un rendement global et un temps d'attente acceptable, dans la réponse aux événements générés de manière externe.
C'est une mauvaise habitude d'inclure des suppositions spécifiques concernant les interfaces externes dans toute l'application. Il est par ailleurs inefficace d'avoir plusieurs unités d'exécution de contrôle arrêtées, dans l'attente d'un événement. Au lieu de cela, attribuez à un seul objet la tâche de détection de l'événement. Lorsque se produit l'événement, cet objet peut notifier les autres objets qui ont besoin d'informations au sujet de cet événement. Cette conception est basée sur un schéma de conception bien connu et éprouvé, le pattern "Observateur" [GAM94]. Il peut facilement être étendu pour une plus grande flexibilité au pattern "Diffuseur-Abonné", où un objet diffuseur agit comme intermédiaire entre les détecteurs d'événements et les objets intéressés par l'événement ("abonnés") [BUS96].
Les actions d'un système peuvent être déclenchées par l'occurrence des événements générés de manière externe. Un événement majeur généré de manière externe peut être simplement le passage du temps, représenté par le tic-tac de l'horloge. D'autres événements externes proviennent des unités d'entrée connectées au matériel externe, y compris les interfaces utilisateurs, les détecteurs de processus, et tous les liens de communication aux autres systèmes.
Afin qu'un logiciel détecte un événement, il doit soit être bloqué en attente d'une interruption, soit périodiquement vérifier le matériel pour voir si l'événement s'est produit. Dans le dernier cas, le cycle périodique doit pouvoir être court, pour éviter de manquer un événement de courte durée ou des occurrences multiples, ou simplement pour minimiser le délai d'attente entre les occurrences et la détection d'événements.
La chose intéressante à ce sujet est que, même si un événement est rare, le logiciel doit être bloquéen l'attendant, ou il doit opérer une vérification fréquente. Mais beaucoup (sinon la plupart) d'événements traités par un système sont rares ; la plupart du temps, dans tout système donné, rien de significatif ne se produit.
Le système d'ascenseur fournit beaucoup de bons exemples à ce sujet. Les événements majeurs de la vie d'un ascenseur comportent un appel de service, la sélection de l'étage, la main d'un passager bloquant la porte, et le passage d'un étage à l'autre. Certains de ces événements nécessitent une réponse prioritaire, mais tous sont extrêmement rares, comparés à l'échelle de durée des temps de réponse désirés.
Un simple événement peut déclencher beaucoup d'actions, et les actions peuvent dépendre de l'état des divers objets. De plus, les différentes configurations d'un système peuvent utiliser le même événement de manière différente. Par exemple, quand un ascenseur passe un étage, l'affichage dans la cabine de l'ascenseur doit être mis à jour et l'ascenseur lui-même doit savoir où il se trouve, de manière à pouvoir répondre aux nouveaux appels et aux sélections d'étages des passagers. La localisation des étages peut ou non être affichée à chaque étage.
L'interrogation est coûteuse ; elle nécessite que certaines parties du système arrêtent périodiquement leurs activités, si un événement s'est produit. Si l'événement demande une réponse rapide, le système devra vérifier assez fréquemment l'arrivée des événements, en limitant ensuite la quantité des autres travaux qui peuvent être accomplis.
Il est beaucoup plus efficace d'attribuer une interruption à un événement, avec le code dépendant de l'événement activé par l'interruption. Bien que les interruptions soient parfois évitées, du fait qu'elles sont considérées comme "coûteuses", une utilisation judicieuse des interruptions peut être bien plus efficace qu'une interrogation répétée.
Les cas où les interruptions seraient privilégiées comme un mécanisme de notification d'événements sont ceux dans lesquels la survenue des événements est aléatoire et peu fréquente, si bien que la plupart des interrogations révèlent que l'événement ne s'est pas produit. Les cas où l'interrogation serait privilégiée, se produisent d'une manière régulière et prévisible et la plupart des efforts d'interrogation révèlent que l'événement s'est produit. Au milieu, il y aura un passage au niveau duquel, on sera indifférent par rapport au fait que le comportement est un comportement d'interrogation ou réactif. L'un ou l'autre fera l'affaire et le choix importe peu. Dans la plupart des cas, néanmoins, étant donné le caractère aléatoire des événements dans le monde réel, le comportement réactif sera privilégié.
La diffusion des données (habituellement des signaux) est coûteuse et peu rentable. Seuls quelques objets sont intéressés par les données, mais tous doivent s'arrêter pour les examiner. Une meilleure approche, consommant moins de ressources, est l'utilisation de la notification pour informer exclusivement les objets intéressés par la survenue d'un événement. Restreignez la diffusion aux événements qui nécessitent l'attention de nombreux objets (par exemple les événements de temporisation et de synchronisation).
Plus spécifiquement :
Il se peut que les instructions les plus importantes pour le développement d'applications concurrentes efficaces consistent à optimiser l'utilisation de mécanismes d'accès concurrents légers. Le matériel et le logiciel du système d'exploitation jouent tous deux un rôle dans le support des accès concurrents ; en revanche, ils fournissent tous deux des mécanismes relativement lourds, laissant ainsi une grande quantité de travail au concepteur de l'application. Nous devons combler le gouyffre entre les outils disponibles et les besoins des applications concurrentes.
Les objets actifs aident à combler ce gouffre, par l'intermédiaire de deux caractéristiques clés :
Les objets actifs constituent également un environnement idéal pour les objets passifs produits par les langages de programmation. La conception d'un système entier à partir d'objets concurrents, sans utiliser des artefacts de procédure, tels que les programmes et les processus, conduit à des conceptions plus modulaires, cohésives et compréhensibles.
Dans la plupart des systèmes, moins de 10 % du code utilise plus de 90 % des cycles de l'UC.
Beaucoup de concepteurs de systèmes agissent comme si chaque ligne de code devait être optimisée. Au lieu de cela, concentrez-vous sur l'optimisation des 10 % qui s'exécutent le plus souvent ou qui utilisent davantage de ressources. Concevez les autres 90 % en mettant en valeur l'aspect de compréhension, de maintenabilité, de modularité et en soulignant la facilité de mise en oeuvre.
Les conditions non fonctionnelles et l'architecture du système vont affecter le choix des mécanismes utilisés pour implémenter les appels de procédure distants. Une vue d'ensemble des compromis entre les différentes alternatives possibles est présentée ci-dessous.
Mécanismes | Utilisations | Commentaires |
---|---|---|
Messagerie | Accès asynchrone aux serveurs de l'entreprise | La couche logicielle intermédiaire de messagerie peut simplifier la tâche de programmation de l'application, en gérant les files d'attente, les expirations de délai et les conditions relatives à la reprise et au redémarrage. Vous pouvez aussi utiliser la couche logicielle intermédiaire de messagerie en mode pseudo-synchrone. Habituellement, la technologie liée aux messageries peut supporter des messages de grande taille. Certaines approches d'appels de procédure distants peuvent être limitées par rapport à la taille des messages, nécessitant une programmation supplémentaire pour traiter les messages de grande taille. |
Connectivité JDBC/Interface ODBC | Appels de bases de données | Ce sont des interfaces indépendantes des bases de données pour les servlets Java ou les programmes d'applications, permettant d'effectuer des appels vers des bases de données qui se trouvent soit sur le même serveur, soit un autre serveur. |
Interfaces natives | Appels de base de données | La plupart des distributeurs de bases de données ont implémenté des interfaces de programmes d'applications natives pour leurs propres bases de données, qui offrent un niveau de performances supérieur à ODBC, au prix de la portabilité des applications. |
Appel de procédure distant | Pour appeler des programmes ou des serveurs distants | Il se peut que vous n'ayez pas à programmer au niveau de l'appel de procédure distant, si votre générateur d'applications s'en charge pour vous. |
Conversationnel | Applications e-business peu utilisées | Habituellement, la communication programme à programme qui utilise des protocoles comme APPC ou Sockets. |
Beaucoup de systèmes nécessitent un comportement concurrent et des composants répartis. La plupart des langages de programmation nous aident peu, par rapport à l'une ou l'autre de ces questions. Nous avons vu que nous avons besoin de bonnes abstractions pour comprendre les besoins en termes d'accès concurrent dans les applications, ainsi que les options pour les implémenter dans le logiciel. Nous avons également vu que, paradoxalement, tandis que le logiciel concurrent est de manière inhérente plus complexe que le logiciel non concurrent, il est également capable de simplifier amplement la conception des systèmes susceptibles de traiter les accès concurrents dans le monde réel.
RUP (Rational Unified Process)
|