Lancer plusieurs processus sans utiliser de threads (Labortablo – 3ème partie)

Mon ordinateur est équipé de deux écrans. Par conséquent, j’ai fait en sorte que mon interface de bureau moulé à la louche, prenne en compte ce cas de figure. Dans un premier temps, j’ai utilisé le module multiprocessing. Cela me semblait être la solution idéale pour lancer simultanément un process sur chaque écran.

Seulement, il y a un léger problème. threading et multiprocessing rentrent en conflit avec des interfaces graphiques telles que tkinter. Le résultat final présente des signes d’instabilité qui ne sont pas du meilleur effet. En théorie, on peut utiliser multiprocessing avec tkinter mais toutes les interactions avec l’interface graphique doivent se faire par un seul et unique thread. Le module multiprocessing peut traiter des données et faire des calculs dans plusieurs threads, mais il ne peut pas « multithreader » tkinter! Les deux processus doivent être séparés.

Donc, dans un souci d’amélioration continue, je me suis mis en quête d’une solution alternative, et je pense avoir codé quelque chose qui comble mes attentes en terme de stabilité. Alors comment ai-je procédé? En fait, je suis parti de l’idée toute simple que pour deux écrans, il faut avoir non pas deux threads d’un même programme mais deux programmes! Et je voulais que tout le processus se déroule de manière automatique. Pas question pour l’usager, d’ouvrir un fichier de configuration pour en modifier le contenu. L’objectif était vraiment de coder quelque chose qui s’installe tout seul et qui vit sa vie sans intervention de maintenance. Mais comment faire pour donner l’illusion de lancer et d’arrêter deux threads?

  1. Situation initiale

Voici la configuration initiale avec multiprocessing.

2. Suppression de multiprocessing, modification du code et explications détaillées

Au démarrage de l’ordinateur, le programme se lance grâce au fichier start.py dont le chemin se trouve dans ~/.config/openbox/autostart.

Examinons, si vous le voulez bien, ce script. La première opération qu’il exécute, consiste à détruire tous les fichiers et répertoires antérieurs qui ne correspondent pas à la configuration par défaut (en monomoniteur, si vous me permettez ce néologisme). Il est nécessaire de partir sur une base propre:

Version standard monomoniteur

Pourquoi détruire la configuration multi-écrans à chaque démarrage? Eh bien parce que l’utilisateur peut très bien décider, un beau matin, de suppprimer un écran ou d’en rajouter un troisième. Je ne suis pas dans sa tête et je ne connais pas ses intentions.

Ensuite, vient le moment de déterminer le nombre de moniteurs. Plutôt que de compter avec ses doigts, confions cette tâche à la commande suivante:

xrandr --listactivemonitors

Résultat:

Monitors: 4
 0: +*HDMI-1 1920/698x1080/392+1920+0  HDMI-1
 1: +VGA-1 1920/477x1080/268+0+0  VGA-1
 2: etc...
 3: etc...

Je crois que c’est clair… Maintenant, le programme va créer autant de répertoires qu’il y a de moniteurs supplémentaires, et il va copier le contenu de labortablo dans chacun d’entre eux. Si vous avez quatre écrans, il va donc créer en sus de labortablo (qui est en quelque sorte le « répertoire zéro« ):

  • labortablo_1
  • labortablo_2
  • labortablo_3

Nous nous retrouvons donc avec cette configuration:

Version quadrimoniteur

Voici la ligne de code qui effectue cette opération:

if i > 0:
    shutil.copytree(
        f'{self.cwd}/labortablo',
        f'{self.cwd}/labortablo_{i}'
        )

De cette manière, si je veux apporter des modifications au code avant de le pousser dans mon dépôt Git, il me suffit de modifier labortablo, le « répertoire zéro ». Je sais qu’au prochain démarrage, les modifications seront prises en compte pour tous les autres programmes.

Ensuite le script start.py se charge de traiter les informations relatives aux écrans. Puis, il créé un fichier coords.txt pour chaque interface de bureau. Ce fichier est construit de telle sorte qu’il puisse se transformer très facilement en dictionnaire. Je trouve que cette syntaxe facilite bien la vie.

w:1920   # largeur
h:1080   # hauteur
x:0      # abscisse
y:0      # ordonnée

Ainsi, je peux passer rapidement ces paramètres aux classes qui en ont besoin. Tous les éléments graphiques vont récupérer ces informations pour configurer l’interface de bureau sur chaque écran.

args = dict()
with open('coords.txt', 'r') as rfile:
    for line in rfile.readlines():
        args[line.split(':')[0]] = int(line.strip().split(':')[1])

Le moment est venu de créer le fichier start.sh. Il va contenir tous les programmes à lancer et il sera détruit au prochain démarrage. Donc, dans le cas de figure ou il y a quatre écrans, voici à quoi ressemble ce script:

#!/bin/bash

/home/miamondo/chemin/vers/labortablo/taskbar.py &
/home/miamondo/chemin/vers/labortablo/categories.py &
/home/miamondo/chemin/vers/labortablo_1/taskbar.py &
/home/miamondo/chemin/vers/labortablo_1/categories.py &
/home/miamondo/chemin/vers/labortablo_2/taskbar.py &
/home/miamondo/chemin/vers/labortablo_2/categories.py &
/home/miamondo/chemin/vers/labortablo_3/taskbar.py &
/home/miamondo/chemin/vers/labortablo_3/categories.py &

Alors, vous allez me dire qu’il ne s’agit pas d’un script Python. Par conséquent, je ne devrais pas affirmer que Labortablo est une interface de bureau entièrment codée en Python. C’est vrai. Je le reconnais. Mais je n’ai pas d’autres choix si je veux lancer mon métaprogramme. On peut très bien lancer des fichiers *.py à partir d’un fichier Python mais j’ai ouï-dire que ce genre d’hérésie conduisait tout droit au bûcher.

Avec Python, on lance des modules, pas des fichiers, et à ce stade des opérations, je ne peux pas car il faudrait que j’utilise threading ou multiprocessing. C’est le python qui se mord la queue!

Enfin, dernière opération, je rend le fichier start.sh exécutable. Il faut le faire à chaque fois car il est détruit à chaque démarrage. Puis start.sh est lancé par start.py.

Conclusion

J’ai bien conscience du fait que cette manière de procéder est loin d’être académique mais d’une part, je m’en fiche un peu, et d’autre part ça tourne comme une horloge. J’utilise cette interface de bureau tous les jours, non pas dans une machine virtuelle mais sur mon PC principal. Il faut encore que je lui rajoute la gestion des catégories et des applications (ajouts/suppressions). Il faut aussi que je fasse le point sur tout ce qu’il y a à installer pour faire tourner le programme (xrandr, wmctrl, xprop etc…)

Voilà… Je n’oublie pas de vous souhaiter un Joyeux Nowouel et une bonne vaccination à toutes et à tous.

2 commentaires sur « Lancer plusieurs processus sans utiliser de threads (Labortablo – 3ème partie) »

  1. Bonjour et merci pour tes articles intéressants. Je suis cette série sur labortablo depuis son début.

    J’ai 2 petites remarques :

    – je ne comprends pas bien pourquoi tu as besoin d’1 processus/thread par écran ?
    – tu crée un script shell pour lancer différent processus parce que c’est mieux de lancer des processus en shell qu’en python et tu as au final start.py → start.sh → la série de categorie.py/taskbar.py. Tu lance donc bien des processus en python… (je ne suis pas sûr de comprendre en quoi il est mal de lancer des processus en python à vrai dire)
    – (bon en fait j’ai un troisième point) tu ne fais aucun contrôle de processus (se sont-ils bien lancés au démarrage ? ont-ils crashés plus tard ? stockage de l’erreur et relancer en cas d’erreur ? Je présume que start.py, fais plus que démarré mais reste en tout le long de la session, il n’a pourtant plus aucun lien avec les différents processus, ils ne peut donc pas les relancer et gérer leurs erreurs. Comment se passe le redémarrage de labortablo ? Tu ne te retrouve pas avec des processus en trop ?

    Il me semble que même si ça demande un peu de travail en plus tu gagnerais à utiliser multiprocessing en passant en paramètre à chaque fils les coordonnées dont ils ont besoin. Ça évite d’avoir à recopier/supprimer l’arborescence et à créer un fichier de script et permettra d’avoir un contrôle plus fin entre le père et les fils (mieux gérer les redémarrage, potentiellement gérer les rechargements de configuration).

    Ce n’est bien sûr qu’une idée, tu en fait bien ce que tu veux. Loin de moi l’idée de te dire ce quoi faire c’est plus pour échanger autour de ton projet 🙂

    Bonne fin d’année à toi et à tes proches

    J'aime

    1. Bonjour Barmic,
      Merci pour ton long commentaire. Je suis heureux de pouvoir échanger sur mon projet. Je serais même encore plus heureux si d’autres voulaient y participer. Je vais essayer de répondre à tes questions, je dis bien « essayer » car je fais ça en amateur, et il se peut qu’il y ait quelques approximations dans mes explications.

      Première question
      J’utilise Tkinter comme GUI. Il serait en effet possible d’instancier une seule fenêtre tk.Tk() (par exemple le bouton avec le menu hamburger sur l’écran zéro) et ensuite de créer uniquement des fenêtres Toplevel toutes enfants de cette fenêtre principale Comme ça, tkinter ne serait pas « multithreadé ». Mais pour cela, il faudrait que je reprenne mon code de A à Z et je ne suis pas très chaud pour le faire, car ma construction actuelle est vraiment stable. Aucune barre de tâches ne disparait subitement. Aucun plantage. Démarrage au quart de tour.

      Deuxième question
      Tu as raison. Il est possible de le faire. Du coup, j’ai essayé de me débarrasser du script start.sh qui me semblait inutile mais curieusement, je n’y arrive pas. Le résultat n’est pas le même. L’interface ne se lance pas comme il faut. Je vais regarder ça de plus près.

      Troisième question
      Je n’ai jamais de processus en trop. S’il y a un seul écran, un seul Labortablo est lancé. S’il y en a deux, deux Labortablos sont lancés. Mais il faut tout de même que je m’occupe maintenant de contrôler les processus et de capturer les erreurs, comme tu me le préconises à juste titre. Je suis à la recherche d’une solution satisfaisante et ce sera le thème de mon prochain article.

      Un grand merci en tout cas pour tous tes bons conseils.
      Au plaisir de te relire,
      Benoît

      J'aime

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s