Échange de données multiprocess

Jean-Yves FOCK-HOON

Les commandes 4D pour communiquer entre process sont faciles à utiliser. Le problème, c’est que vous devez rester synchronisé. Pouvez-vous le faire ? Le minutage est important. La perte d’un événement peut être désastreuse. Chaque fois que vous manipulez les variables d’un autre process, surtout les tableaux, vous devez vérifier qu’un autre process n’est pas en train de faire la même chose. ♪

- Titre : Échange de données multiprocess

- Auteur : Jean-Yves FOCK-HOON

- Parution : 1 décembre 2005

- Licence : Ce document est issu de https://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- Résumé

- Introduction

- Pourquoi utiliser ces commandes ?

- Exemple 1 : une communication simple

- Exemple 2 : une communication plus complexe

- Exemple 3 : utiliser un fichier journal

- Exemple 4 : vice-versa

- Exemple 5 : le même scénario que l’exemple 4

- Base exemple

Résumé

Si votre base de données exécute plusieurs process, ces process peuvent avoir besoin de communiquer entre eux pour lire ou écrire des valeurs dans des variables process. Quoique ce type de communication puisse paraître simple, il peut s’avérer un véritable cauchemar. Cette note technique propose quelques exemples sur la manière d’affecter ou de lire des valeurs dans des process. Ces exemples devraient vous donner une idée de la manière de vous assurer que la communication entre process reste simple en illustrant comment synchroniser la communication.

Introduction

Pour notre objectif, les exemples utiliseront trois commandes simples :

ECRIRE VARIABLE PROCESS permet d’attribuer une valeur à une variable spécifique définie dans le process de destination ;

LIRE VARIABLE PROCESS permet de lire la valeur d’une variable spécifique définie dans le process source ;

VARIABLE VERS VARIABLE permet d’attribuer une valeur à une variable spécifique définie dans le process de destination.

Ces commandes sont bien décrites dans la documentation. Souvenez-vous seulement que toutes les variables doivent être définies dans les deux process, le process source et le process de destination, et que la seule différence entre ECRIRE VARIABLE PROCESS et VARIABLE VERS VARIABLE est que seule la commande VARIABLE VERS VARIABLE permet d’utiliser des tableaux.

Pour la documentation sur ces commandes, consultez :

ECRIRE VARIABLE PROCESS

LIRE VARIABLE PROCESS

VARIABLE VERS VARIABLE

Pourquoi utiliser ces commandes ?

Ces commandes servent surtout dans les cas où un process attend une valeur pour poursuivre son exécution.

Par exemple, supposez que vous faites tourner une application 4D avec de multiples process et que vous voulez quitter l’application. Quelles sont vos options dans cette situation ? Vous ne pouvez pas vous contenter de quitter 4D comme cela et laisser l’application tuer les process, parce qu’il peut y avoir des données à sauver. Votre premier choix pourrait plutôt être de créer un sémaphore. Tous les process devraient alors vérifier si ce sémaphore particulier a été créé et, dans ce cas, se terminer. Une autre option serait de définir une variable interprocess, comme, par exemple, <>Quit4D. Dans ce cas, tous les process auraient toujours à tester cette variable aussi souvent que possible, et se terminer si besoin.

Mais qu’en est-il si vous ne voulez pas quitter tous les process ? Et si vous voulez juste quitter un process particulier ? Ou peut-être seulement deux ?

Dans ce cas, vous pouvez toujours créer de multiples variables interprocess, une pour chaque process. Mais ce n’est pas une solution viable, surtout quand vous ne pouvez pas déterminer le nombre de process qui peuvent être lancés. Alors pourquoi ne pas utiliser un tableau interprocess qui contiendrait les n° de process de tous les process à terminer ? Dans ce cas, le process devrait vérifier ce tableau et tester si son numéro de process y figure. Oui, ça marcherait, mais ce n’est pas si simple d’utiliser un tableau interprocess. Les process doivent ajouter et supprimer des lignes. Supposons, par exemple, que vous terminez le process n° 5. Après l’avoir fermé, vous devrez l’enlever du tableau parce que vous pouvez créer un autre process qui réutilisera le même numéro.

Au lieu de cela, ne serait-il pas plus simple de tester une variable globale ? Si un process décide qu’un autre process doit se terminer, il peut simplement transmettre l’ordre à l’aide d’une variable globale spécifique et la question est réglée.

Les variables interprocess sont très utiles, mais comme elles sont communes à tous les process, il est difficile de les utiliser pour communiquer uniquement entre des process particuliers. Il est parfois préférable d’accéder directement à des variables globales sans utiliser aucune variable interprocess.

C’est un cas où nous pouvons utiliser nos trois commandes. La question suivante est comment dois-je les utiliser ? Dans notre exemple, c’est une situation de « tire et oublie ». Mais, quelquefois, il faut établir un long dialogue entre ces process. Vous ne voulez pas terminer un process ; vous voulez lire des données et en renvoyer dans les deux sens. Dans ce cas, l’utilisation de ECRIRE VARIABLE PROCESS avec LIRE VARIABLE PROCESS ou VARIABLE VERS VARIABLE n’est pas un problème. Ce qui est un vrai problème, c’est la synchronisation de cette communication.

Exemple 1 : une communication simple

Dans la base de données fournie avec cette note technique, le process principal propose cinq boutons exemples. Nous allons cliquer sur le premier bouton, qui va lancer une communication très basique. Le clic sur le premier bouton crée deux process, Process Un et Process Deux. Process Un va démarrer un dialogue avec Process Deux et vous allez voir s’afficher les échanges dans ce dialogue. Process Un affichera tous les messages reçus de Process Deux et vice-versa.

Une fois la communication établie, tous les messages sont envoyés et reçus. Ces deux process sont créés par le process principal, Process Un, suivi de Process Deux. Vous pouvez maintenant examiner les deux méthodes, M_ProcessOne et M_ProcessTwo. Vous pouvez constater que Process Un exécute une boucle sur la variable <>Ready en attendant que Process Deux soit prêt (c’est-à-dire qu’il ait initialisé ses propres variables). Cela fait, Process Deux va mettre cette variable interprocess à Vrai et Process Un pourra continuer. Ce test a été ajouté pour garantir que nous sommes prêts à établir une communication. En d’autres termes, avant de démarrer une communication, il faut s’assurer que le process de destination est disponible.

Ensuite, Process Un envoie le premier message à Process Deux à l’aide de la commande ECRIRE VARIABLE PROCESS. Il affecte le message à la variable vReceivedMessage. Process Deux possède cette variable avec la valeur vide. Il attend que cette valeur soit non vide pour afficher le message reçu et envoyer une réponse. Après avoir envoyé le premier message, Process Un exécute la même boucle avec sa propre variable vReceivedMessage, en attendant que Process Deux lui affecte une nouvelle valeur. Ainsi, vous pouvez voir que Process Un envoie un premier message, puis attend la réponse ; Process Deux attend l’arrivée d’un message, puis envoie la réponse ; Process Un reçoit la réponse et envoie un autre message, etc.

Cet exemple de communication est simple. Chaque process attend son tour pour communiquer. C’est une communication synchronisée. Le même schéma peut s’appliquer à un navigateur Web qui vient de demander une page à un serveur Web. Aujourd’hui, cette technique n’est plus vraiment utilisée parce qu’elle a été optimisée. Il peut être inefficace de devoir envoyer une requête et attendre la réponse avant d’envoyer la requête suivante.

Exemple 2 : une communication plus complexe

Ce serait agréable si des process pouvaient communiquer sans devoir attendre d’autres process. Dans l’exemple 2, nous avons trois process d’entrée (méthode M_ProcessInput2) qui ont leurs propres données prédéfinies stockées dans des tableaux. Nous voulons les envoyer toutes à un unique process de sortie, c'est-à-dire à un unique tableau. Nous voulons que cette communication soit facile. Nous ne voulons pas que le process de sortie gère le premier événement, puis vérifie le second. Cela voudrait dire que chacun des trois process d’entrée devrait attendre son tour pour envoyer ses données au process de sortie (méthode M_ProcessOutput2). Donc, nous allons utiliser un tableau interprocess comme mémoire tampon. Chacun des trois process devra remplir cette mémoire tampon. Comme deux process peuvent tenter d’ajouter un élément en même temps, il est nécessaire d’utiliser un sémaphore pour éviter de tels conflits. Dans ce cas, nous pouvons voir notre mémoire tampon augmenter et empiler tous les événements.

Dans notre exemple, le process de sortie est très lent ; nous avons utilisé un ENDORMIR PROCESS pour simuler ce ralentissement. C’est pourquoi tous les process d’entrée ne peuvent s’offrir le luxe d’attendre le process de sortie. Nous avons apporté une autre amélioration qui est que le process de sortie ne va pas tester le prochain élément disponible. Il va seulement créer le même sémaphore utilisé par les autres process pour copier localement cette mémoire tampon, puis l’effacer. Tous les autres process d’entrée peuvent alors ajouter des données dans cette mémoire tampon vide pendant que le process de sortie traite toute l’information empilée.

Pour rendre les choses plus complexes, nous allons tout traiter depuis une fenêtre de dialogue, au lieu d’exécuter le traitement dans un process simple. Si vous ouvrez une fenêtre, c’est pour afficher le statut du process, ce qui signifie qu’après chaque calcul, vous devrez redessiner la fenêtre. Comme vous le savez, 4D redessine la fenêtre uniquement lorsque le process est inactif. Pour un process d’entrée, le cas est simple. Vous pouvez utiliser l’événement Sur minuteur. Cet événement créera le sémaphore, ajoutera l’élément, effacera le sémaphore et attendra le prochain Sur minuteur pour traiter l’élément suivant. Une fois l’élément ajouté, le process de sortie doit le traiter. Comme le process d’entrée, le process de sortie ne peut pas tourner indéfiniment. Il doit être inactif pour redessiner l’interface. D’ailleurs, ce serait un gaspillage de temps CPU de laisser ce process tourner lorsque rien ne se passe.

La meilleure solution serait de faire un APPELER PROCESS. Une fois qu’un élément a été ajouté à la mémoire tampon, le process d’entrée fera un APPELER PROCESS au process de sortie. Le problème ici est que nous avons défini un process de sortie plus lent que notre process d’entrée. Lorsque 4D exécute un événement formulaire, il ne peut pas répondre aux autres événements. Par exemple, si vous exécutez un Sur chargement, un Sur clic souris ou un Sur appel extérieur et que vous recevez en même temps un Sur appel extérieur, ce dernier événement ne sera pas empilé. Il sera perdu. Vous devez vous assurer que si un Sur appel extérieur est en cours d’exécution, il faut néanmoins gérer vos tableaux. C’est pourquoi le process de sortie utilise un FIXER MINUTEUR (30). Vous pouvez mettre à jour la fenêtre, puis retester votre tableau, à tout hasard. Si vous avez reçu de nouveaux éléments, vous pouvez les gérer, puis retester 30 ticks plus tard, jusqu’à ce qu’il n’y ait plus de nouveaux éléments. Vous pouvez alors tester un moment après, par exemple 500 ticks.

Comme vous le voyez, vous ne pouvez pas vous permettre de laisser un process utiliser tout le temps CPU pour rien. Cela dégraderait la performance. Lorsque vous synchronisez des process avec des dialogues, il faut les endormir pour redessiner vos fenêtres. Pour cela, vous pouvez utiliser les événements Sur minuteur ou Sur appel extérieur. Vous ne pouvez pas toujours travailler en mode synchrone et vous êtes parfois contraint de travailler en mode asynchrone. Un système tel qu’une mémoire tampon est alors nécessaire. Dans cet exemple, la mémoire tampon est un tableau interprocess. Si vous ne pouvez pas utiliser un tableau interprocess, vous pouvez toujours utiliser un tableau process défini dans ce process de sortie. Votre process de sortie aura une variable définissant le nombre d’éléments que la mémoire tampon peut gérer. Chaque process d’entrée devra créer un sémaphore pour lire cette variable, l’incrémenter de 1, si possible, et exécuter un ECRIRE VARIABLE PROCESS sur cet élément particulier. Dans ce cas, cette mémoire tampon a un nombre d’éléments fixé, ce qui peut aisément provoquer des pertes d’événements et générer de nombreuses erreurs. La synchronisation est très importante. De tels cas se produisent dans presque toutes les bases de données. L’erreur la plus commune se produit lorsqu’on utilise un process séparé, par exemple une palette, pour se déplacer dans une sélection. Par exemple, le clic sur un bouton Enregistrement suivant va exécuter un APPELER PROCESS en direction de la fenêtre qui affiche un enregistrement. Mais, si vous cliquez trop vite, votre palette peut traduire que vous affichez le dernier enregistrement, alors que l’autre fenêtre affiche toujours un enregistrement qui n’est pas le dernier.

Exemple 3 : utiliser un fichier journal

On peut, bien sûr, utiliser la même technique sans dialogues. Vous voulez toujours faire tourner des process multiples qui tentent d’envoyer leur information à un unique process. Dans ce cas, vous aurez besoin d’utiliser des sémaphores et un tableau défini comme mémoire tampon. Cette mémoire tampon sera partagée entre tous les process d’entrée et le process de sortie. Il ne sera pas nécessaire d’utiliser APPELER PROCESS, parce qu’on n’utilisera pas de dialogues. Vous utiliserez seulement le sémaphore pour écrire dans votre tableau.

Voici un exemple parfait d’écriture de votre fichier journal.

Le process de sortie sera un process qui écrit vos informations pour déboguer dans un fichier texte. Tous les process d’entrée seront des process génériques qui exécutent leur tâche habituelle. Un tel système peut vous aider à déboguer votre application, par exemple en détectant une désynchronisation entre vos process, à tracer un profil de votre code, à détecter les méthodes les plus utilisées, ou même à tracer un plantage. Dans ce cas, dites-vous que le process de sortie doit avoir des réactions très rapides.

Les méthodes utilisées dans cet exemple sont M_ProcessOutput3 et ProcessInput3.

Exemple 4 : vice-versa

Vous venez de voir trois process d’entrée essayer d’envoyer des données à un process de sortie. Un scénario dans lequel nous avons tenté de résoudre beaucoup de problèmes de désynchronisation en utilisant seulement des sémaphores et un tableau. Mais, bien entendu, c’est vrai aussi lorsque de multiples process de sortie tentent de lire des données dans un unique process d’entrée et cela peut se résoudre avec des sémaphores et des tableaux, ainsi que les commandes sur les variables des process.

Dans cet exemple, vous avez un process d’entrée, un process source qui contient deux tableaux, des données et la durée pour traiter chaque donnée. Vous avez trois autres process, des process de sortie qui doivent lire cette donnée dans le process d’entrée. Bien entendu, deux process ne peuvent pas lire la même information. Chaque élément d’information doit être transmis une seule fois. De plus, le process d’entrée doit garder la trace du process qui a reçu l’information.

Pour cela, chaque process d’entrée va tester un sémaphore pour s’assurer qu’un seul à la fois accède à une information. Lorsque le process d’entrée obtient le droit de communiquer avec le process de sortie, il doit s’identifier en fournissant son numéro de process, puis il demande le prochain élément disponible. Comme vous voulez le premier élément, le process d’entrée devra effacer cet élément et stocker le prochain élément dans des variables, pour permettre le test. Le process de sortie peut désormais effectuer un LIRE VARIABLE PROCESS sur ces variables, s’endormir pour une durée calculée avec ces valeurs et demander l’élément suivant.

La logique appliquée à cet exemple n’est pas très habile, mais elle l’est assez pour démontrer l’usage de sémaphores et l’usage de variables servant de sémaphores entre deux process. Dans cet exemple, nous mettons la variable GetNextItem (lire item suivant) à Faux et nous réactivons le process d’entrée. Le process d’entrée va traiter l’information suivante et remettre cette variable à Vrai, avant de s’endormir. En testant cette variable, le process de sortie sait que la requête a été exécutée ; il peut maintenant recevoir l’information, puis effacer le sémaphore pour permettre à un autre process d’entrée de s’emparer de l’élément suivant. Les méthodes utilisées dans cet exemple sont M_ProcessInput1 et M_ProcessOutput1.

Exemple 5 : le même scénario que l’exemple 4

LIRE VARIABLE PROCESS vous permet de lire un élément de tableau. Vous voulez accéder au premier élément de ce tableau. Vous pouvez écrire une variable dans ce process qui définit que le prochain élément disponible est le suivant. Si vous suivez le même scénario que celui décrit dans l’exemple 4, votre process d’entrée peut se contenter de dormir. Tous les autres process vont simplement créer un sémaphore pour accéder à cette variable et lire le contenu du tableau. Le code fourni est plus efficace.

Les méthodes utilisées dans cet exemple sont M_ProcessInput5 et M_ProcessOutput5.

Base exemple

Télécharger la base exemple.