IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

multiprocessing et QProcess


Sujet :

Python

  1. #1
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut multiprocessing et QProcess
    Bonsoir,

    bon je vais être honnête, j'ai pas tout piger à ce que je vais vous demander :p

    je veux permettre le multiprocess sur une liste de travaille très longue...

    j'ai plusieurs milliers d'images que je transforme en texte automatiquement via la commande :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    for File in self.SubImgFiles:
     process.start('tesseract -l {0} "{1}" "{1}"'.format(self.Lang, File))
    je voudrais donc accelerer le traitement en séparant le travail en plusieurs parties pour le partager.

    je me dis qu'il faudrait créer X listes de fichiers et les donner aux différents processeurs.

    j'ai vu multiprocessing qui semble le permettre mais les exemples que j'ai trouvé ne sont pas forcement très parlant.

    Avez vous une idée de la façon de s'y prendre ?


    J'ai posté ici car je ne pense pas que le QProcess soit déterminant dans la façon de s'y prendre.


    Merci et bonne soirée à vous.
    Sous Kubuntu 20.04

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Je ne vois pas trop l'intérêt de multiprocessing: il n'y a pas de code/données Python à partager juste la commande/programme externe à lancer avec différents paramètres.
    Dans tous les cas, on va avoir à lancer N process en parallèle (avec un N qui dépend du nombre de CPU disponibles) et leur soumettre le fichier suivant à traiter.
    Peut être regarder du côté de concurrent.futures qui fait déjà une partie du boulot au dessus des Threads ou de subprocess (au choix).

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    Dans tous les cas, on va avoir à lancer N process en parallèle (avec un N qui dépend du nombre de CPU disponibles) et leur soumettre le fichier suivant à traiter.
    C'est tout à fait l'idée que j'en avais

    Je vais regarder ce que tu me conseilles, j'espere juste que ça lancera pas toutes les commandes sur le meme processeur.

    je reviendrai vers vous pour vous dire ce qu'il en est.

    Merci à toi.

    EDIT : C'est parfait !
    je suis passé de 770s à 215s !
    Franchement pratique !

    voici ma version
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    from concurrent.futures import ThreadPoolExecutor # Permet de le multi calcul
    from psutil import cpu_count # Indique le nombre de cpu
     
     
        def TesseractConvert(self, File):
            ### Création du QProcess avec unification des 2 sorties (normale + erreur)
            process = QProcess()
     
            ### Reconnaissance de l'image suivante
            process.start('tesseract -l {0} "{1}" "{1}"'.format(self.Lang, File))
            process.waitForFinished()
     
            # Décompte du nombre de fichier traité avec création du pourcentage
            self.FilesDone += 1
            self.Pourcentage = int((self.FilesDone * 100) / self.TotalSubtitles)
            print(str(self.Pourcentage))
     
     
    with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
        for File in self.SubImgFiles:
             executor.submit(self.TesseractConvert, File)
    Sous Kubuntu 20.04

  4. #4
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    Bonjour,

    je me permets de relancer vite fait ce topic,

    en effet mes threads modifient tous une QProgressBar, ce que qt n'aime pas

    J'ai testé différents trucs mais qui n'ont pas amélioré le systeme...

    Je pense qu'il faut jouer avec les pyqtSlot et pyqtSignal mais ça me parait un peu lourdingue de programmer des centaines voire des milliers de signaux (1 pour chaque thread de mon multiproces)...

    Avez vous une idée pour simplifier le truc ?

    Le code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from concurrent.futures import ThreadPoolExecutor # Permet de le multi calcul
     
    def TesseractConvert(File):
        Reply = LittleProcess('tesseract -l {0} --tessdata-dir "{2}" "{1}" "{1}"'.format(GlobalVar["TesseractLanguage"], File, GlobalVar["TesseractFolder"]))
     
        GlobalVar["FilesDone"] += 1
     
        GlobalVar["ProgressBar"].setValue(GlobalVar["FilesDone"])
     
        ### Si c'était le dernier fichier, on cache la fenetre
        if GlobalVar["FilesDone"] == GlobalVar["TotalSubtitles"]:
            GlobalVar["ProgressDialog"].accept()
     
    with ThreadPoolExecutor(max_workers=GlobalVar["NbCPU"]) as executor:
       for ImageFile in GlobalVar["SubImgFiles"]:
          executor.submit(TesseractConvert, ImageFile)
     
          GlobalVar["ProgressDialog"].exec()
    Sous Kubuntu 20.04

  5. #5
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour hizoka,

    Il n'est pas facile de juger de la validité d'une solution quand on ne connait pas bien le problème à résoudre. Mais imaginons que tu ais plusieurs milliers de tâches indépendantes de durées différentes à traiter par du code Python, voilà la solution qui me plait le plus et que j'ai déjà utilisée:

    - On lance au début du programme et d'un seul coup autant de Process (du module multiprocessing) qu'il y a de CPU, avec 2 arguments: "qin" = Queue d'entrée et "qout" = Queue de sortie. Les 2 queues (du module multiprocessing) seront communes à tous les process ainsi qu'au programme principal et pourront donc être lues et écrites par n'importe lequel d'entre eux (le système de verrous est transparent).

    - le programme principal rentre dans la queue d'entrée "qin" la totalité des travaux à réaliser (p. ex. les fichiers à traiter)

    - Chaque process a un fonctionnement permanent! Au début de chaque boucle while, il attend qu'il y ait un travail en attente dans la queue d'entrée "qin" (travail=qin.get()), le prend, le traite et rentre son résultat dans la queue de sortie "qout" (qout.put(resultat)). Ceci fait, il revient en début de la boucle pour prendre le travail suivant s'il y en a encore.

    - le programme principal compte la longueur de la queue de sortie "qout" et sait que tout est terminé quand le nombre total de travaux réalisés est atteint. Lorsque c'est fait, il peut détruire les process qui sont devenus inutiles. A noter que le suivi par le programme principal de ce nombre de travaux réalisés peut alimenter une barre de progression. Pour ne pas figer la partie graphique, le comptage peut être fait par un timer (il y a d'autres solutions).

    A mon avis, le grand avantage de cette méthode est que ce n'est pas le programme principal qui alimente chaque process: c'est chaque process qui vient chercher du travail quand il a terminé le précédent. Ce qui fait que le process qui est tombé sur un travail de courte durée en prendra plus! Cela veut dire que les ressources des CPU sont complètement exploitées.

    A noter qu'avec ajout de l'instruction "freeze_support()", cette solution est compatible avec cx_freeze.

    Si cette solution t'intéresse, je peux te proposer du code.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  6. #6
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Citation Envoyé par hizoka Voir le message
    en effet mes threads modifient tous une QProgressBar, ce que qt n'aime pas

    J'ai testé différents trucs mais qui n'ont pas amélioré le systeme...

    Je pense qu'il faut jouer avec les pyqtSlot et pyqtSignal mais ça me parait un peu lourdingue de programmer des centaines voire des milliers de signaux (1 pour chaque thread de mon multiproces)...
    D'après ce que j'en lis TesseractConvert(File) est un thread qui va créer un sous process. On va donc lancer autant de threads que de fichiers et créer de s/process... Ce qui effectivement distribue le contrôle sur beaucoup d'agents autonomes.
    Comment simplifier? En virant les agents et en le remplaçant par un "manager" En créant un "manager" qui aura la responsabilité de pousser threads et s/process et de mettre à jour la barre de progression à la fin de chaque s/process.
    Si on reste avec le module concurrent.futures, on peut appeler un callback "à la fin de..." et l'architecture du bazar ressemble à:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    from threading import Thread
    from concurrent.futures import ThreadPoolExecutor as Pool
    import time
     
    def launcher(count, on_update, on_done):
     
        left = count
        step = 100 / count
        def callback(future):
            nonlocal left
            left -= 1
            on_update(step)
            if left == 0:
                on_done()
     
        with Pool(max_workers=2) as pool:
            for z in range(count):
                future = pool.submit(subprocess.call, 'python -c "import time; time.sleep(0.5)"')
                future.add_done_callback(callback)
     
    if __name__ == '__main__':
     
        import tkinter as tk
        from tkinter.constants import *
        from tkinter import ttk
     
        root = tk.Tk()
     
        def updater(root, pb):
            def _do_update(x):
                root.after_idle(lambda: pb.step(x))
            return _do_update
     
        def on_done():
            def reset():
                pb.pack_forget()
                start_btn['state'] = NORMAL
            root.after_idle(reset)
     
        def do_launch():
            start_btn['state'] = DISABLED
            pb.pack()
            update = updater(root, pb) 
            p = Thread(target=launcher, args=(150, update, on_done))
            p.daemon = True
            p.start()
     
        start_btn = tk.Button(root, text='start', command=do_launch)
        start_btn.pack()
        pb = ttk.Progressbar(root, orient=HORIZONTAL, length=200, mode='determinate')
     
        tk.mainloop()
    note: tkinter ou Qt, c'est la même chose côté répartition des tâches et de toutes façons ce qui vous intéresse c'est la partie au dessus de "if __name__ == '__main__':"

    Citation Envoyé par tyrtamos Voir le message
    Il n'est pas facile de juger de la validité d'une solution quand on ne connait pas bien le problème à résoudre. Mais imaginons que tu ais plusieurs milliers de tâches indépendantes de durées différentes à traiter par du code Python, voilà la solution qui me plait le plus et que j'ai déjà utilisée:

    - On lance au début du programme et d'un seul coup autant de Process (du module multiprocessing) qu'il y a de CPU, avec 2 arguments: "qin" = Queue d'entrée et "qout" = Queue de sortie. Les 2 queues (du module multiprocessing) seront communes à tous les process ainsi qu'au programme principal et pourront donc être lues et écrites par n'importe lequel d'entre eux (le système de verrous est transparent).

    - le programme principal rentre dans la queue d'entrée "qin" la totalité des travaux à réaliser (p. ex. les fichiers à traiter)

    - Chaque process a un fonctionnement permanent! Au début de chaque boucle while, il attend qu'il y ait un travail en attente dans la queue d'entrée "qin" (travail=qin.get()), le prend, le traite et rentre son résultat dans la queue de sortie "qout" (qout.put(resultat)). Ceci fait, il revient en début de la boucle pour prendre le travail suivant s'il y en a encore.

    - le programme principal compte la longueur de la queue de sortie "qout" et sait que tout est terminé quand le nombre total de travaux réalisés est atteint. Lorsque c'est fait, il peut détruire les process qui sont devenus inutiles. A noter que le suivi par le programme principal de ce nombre de travaux réalisés peut alimenter une barre de progression. Pour ne pas figer la partie graphique, le comptage peut être fait par un timer (il y a d'autres solutions).

    A mon avis, le grand avantage de cette méthode est que ce n'est pas le programme principal qui alimente chaque process: c'est chaque process qui vient chercher du travail quand il a terminé le précédent. Ce qui fait que le process qui est tombé sur un travail de courte durée en prendra plus! Cela veut dire que les ressources des CPU sont complètement exploitées.

    A noter qu'avec ajout de l'instruction "freeze_support()", cette solution est compatible avec cx_freeze.

    Si cette solution t'intéresse, je peux te proposer du code.
    Très bonne idée, mais hizoka utilise déjà concurrent.futures qui fait déjà tout çà "en standard".
    *edit* J'ai remis ThreadPoolExecutor à la place de Process, car si l'idée est de lancer une commande externe, ce n'est pas utile. Qt dispose d'une bibliothèque semblable (QtConcurrent). Elle ne sait lancer que des Threads (ils n'ont pas de soucis de GIL en C++) mais je ne sais même pas si elle est dans PyQt.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  7. #7
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks,

    Citation Envoyé par wiztricks Voir le message
    Très bonne idée, mais hizoka utilise déjà concurrent.futures qui fait déjà tout çà "en standard"
    Merci pour l'info! Je n'ai pas encore suffisamment compris ce que faisait "concurrent.futures" pour l'utiliser. Je vais creuser le sujet.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  8. #8
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Je viens de faire quelque chose d'amusant: comparer les 2 solutions: celle avec multiprocessing et celle avec concurrent.futures.

    Le principe que j'ai utilisé est le suivant: je crée une liste de 5000 tâches avec pour chacune une durée tirée au hasard entre 10 et 100 millisecondes. Cela donne en gros un temps total de 270 secondes. Chaque processus fait chaque travail en attendant en fait la durée transmise. Je mesure ensuite le temps de traitement avec plusieurs processus, et je compare les 2 valeurs.

    Solution 1 avec multiprocessing:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import time
    from random import randint
    from multiprocessing import Process, Queue
     
    #============================================================================
    def traitement(qin, qout):
        while True:
            n = qin.get()
            time.sleep(n)
            qout.put(n)
     
    #============================================================================
    if __name__ == '__main__':
     
        # création de la liste de travaux
        ttot = 0 # pour le cumul des durées des travaux
        T = Queue() # pile des travaux à réaliser
        ttot = 0 # pour le cumul des durées des travaux
        for i in range(0, 5000):
            duree = randint(10,100)/1000 # tirage au hasard d'un temps de travail
            T.put(duree)
            ttot += duree 
     
        # création et lancement des process de traitement
        R = Queue() # pile des travaux réalisés
        t = time.clock()
        for cpu in range(0,8):
            process = Process(target=traitement, args=(T, R))
            process.daemon = True
            process.start()
        #attente que tout soit fini
        while R.qsize()<5000:
                pass
        t = time.clock()-t

    Solution 2 avec concurrent.futures

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import time
    from random import randint
    from concurrent.futures import ProcessPoolExecutor
     
    #============================================================================
    def traitement(n):
        time.sleep(n)
        return n
     
    #============================================================================
    if __name__ == '__main__':
     
        # création de la liste des 5000 travaux
        ttot = 0 # pour le cumul des durées des travaux
        T = [] # liste des travaux
        for i in range(0, 5000):
            duree = randint(10,100)/1000 # tirage au hasard d'un temps de travail
            T.append(duree)
            ttot += duree 
     
        # exécution des travaux et enregistrement des résultats dans R
        R = []
        t = time.clock()
        with ProcessPoolExecutor(max_workers=8) as executor:
            for travail, resultat in zip(T, executor.map(traitement, T)):
                R.append(resultat)
        t = time.clock()-t

    Comparaison: les 2 solutions donnent à peu près le même résultat: le travail total de 270 secondes est fait en 34 secondes. Cela veut dire qu'on fait le travail 8 fois plus vite sur un PC qui a un CPU à 8 cœurs (en fait 4 cœurs physiques * 2 avec l'hyperthreading). Mais, bien sûr, la solution 2 avec "concurrent.futures" est plus simple à mettre en œuvre. Dans la mesure où concurrent.futures est construit sur la base de multiprocessing, c'est assez rassurant de voir la similitude des résultats.


    Mais la suite est bizarre. Avec 8 cœurs, que les 8 processus permettent de diviser le travail à réaliser par 8 parait logique, mais si je crée 16 processus, je divise le temps par presque 16. Je peux essayer aussi avec 32 et même plus: avec 50 processus, je divise le temps par environ 40! Ceci avec les 2 solutions. Au delà, je ne gagne plus rien et ça augmente même.

    J'aimerais bien comprendre ça...
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Citation Envoyé par tyrtamos Voir le message
    Mais la suite est bizarre. Avec 8 cœurs, que les 8 processus permettent de diviser le travail à réaliser par 8 parait logique, mais si je crée 16 processus, je divise le temps par presque 16. Je peux essayer aussi avec 32 et même plus: avec 50 processus, je divise le temps par environ 40! Ceci avec les 2 solutions. Au delà, je ne gagne plus rien et ça augmente même.

    J'aimerais bien comprendre ça...
    Si le travail se réduit à:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    def traitement(n):
        time.sleep(n)
        return n
    Il va effectivement en lancer beaucoup pour charger un CPU.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  10. #10
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    onsoir, merci pour vos retours.

    En fait mon systeme marche tres bien mais j'ai le droit en fait à des retours
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    QObject::startTimer: Timers cannot be started from another thread
    QObject::killTimer: Timers cannot be stoped from another thread
    ...
    et avec ta proposition wiztricks, j'ai également les messages.

    Je pensais que ta soluce allait me régler le souci, désolé de ne pas l'avoir signalé.
    Sous Kubuntu 20.04

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Citation Envoyé par hizoka Voir le message
    et avec ta proposition wiztricks, j'ai également les messages.

    Je pensais que ta soluce allait me régler le souci, désolé de ne pas l'avoir signalé.
    Ça c'est un problème entre le thread qui gère la meute de process et le GUI graphique. C'est au dessous du "if __name__ == '__main__':" entre cette thread et la progress bar. Mais vous avez plein d'exemples faits par Tyrtamos comme celui-ci. Il suffit de comprendre les 2 logiques et de les coller ensemble.

    Si vous ne vous en sortez pas, je verrais ce que je peux faire ce week end.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  12. #12
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    Bonjour,

    c'est en effet ce à quoi j'avais pensé en 1er lieu (je lis dès que possible Tyrtamos) mais le truc c'est que je vois pas trop comment l'utiliser dans le cas présent...

    pour éviter que je fasse des milliers de connections, dois je imaginer de lancer le multi thread via un thread et que ce soit lui qui émette les retours d'infos ?
    lancement : thread principal => thread secondaire => multi threads
    reccup infos : multi threads => thread secondaire via la fonction callback => thread principal via l'emit

    cela permettrait il d'éviter que ce soir trop lourd et de créer des milliers de connexions ?

    J'attends vos conseils avisés
    Sous Kubuntu 20.04

  13. #13
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Citation Envoyé par hizoka Voir le message
    pour éviter que je fasse des milliers de connections, dois je imaginer de lancer le multi thread via un thread et que ce soit lui qui émette les retours d'infos ?
    C'est ce que fait mon exemple: il suffit de remplacer le thread principal par un Thread Qt.

    Citation Envoyé par hizoka Voir le message
    cela permettrait il d'éviter que ce soir trop lourd et de créer des milliers de connexions ?
    Le gros soucis dans le code que vous avez montré est ici:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    def TesseractConvert(File):
         ...
        GlobalVar["FilesDone"] += 1
        ...
    Si vous voulez que çà fonctionne, il faudrait verrouiller l'opération avec un mécanisme de lock... Sinon, parfois vous n'arriveriez jamais à terminer. Mais c'est quand même plus simple de faire ce comptage dans un seul Thread Qt qui pourra s'interfacer assez simplement avec la Progress Bar plutôt que d'avoir à coder à la main cette interface avec des threads non-Qt.
    In fine, le résultat est le même mais on essaie d'utiliser au mieux l'existant pour écrire moins de lignes.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  14. #14
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    Oups, mauvais lecture de votre code !
    Ce qui me rassure c'est que j'avais une idée correcte

    Je viens de mettre le systeme en place et tout fonctionne... presque... j'ai toujours ces messages :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    QObject::killTimer: Timers cannot be stopped from another thread
    QObject::startTimer: Timers cannot be started from another thread
    mais je ne pige vraiment pas pourquoi...
    mon QThread émet bien la progression, qui est récupérer par une fonction dans le thread principal et la bar est mise à jour.
    J'ai essayé en passant par une fonction dans la classe de la fenêtre de la progressbar mais le résultat est le même...

    Mais il faut que je reparte de zéro car les exemples de Tyrtamos fonctionnent...

    Je reviens vers vous si je n'y arrive pas !
    Sous Kubuntu 20.04

  15. #15
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Juste un petit complément au cas où.

    Un thread utilisant QThread est nécessaire pour qu'il soit capable d'envoyer (.emit(...)) des messages au graphique pour, par exemple, mettre à jour une barre de progression.

    Mais il arrive qu'on préfère utiliser threading de Python pour le thread. Si c'est le cas, il est possible de lui faire émettre quand même des messages au graphique PyQt de la façon suivante: on utilise une classe qui hérite en même temps de threading et QObject (héritage multiple)!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  16. #16
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Avec Qt, çà donnerait un truc comme çà:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    from concurrent.futures import ThreadPoolExecutor as Pool
    import subprocess
    from PyQt5.QtCore import QThread, pyqtSignal
     
    class Launcher(QThread):
        on_update = pyqtSignal(float)
        on_done = pyqtSignal()
        _abort = False            
     
        def __init__(self, count=10):
            super().__init__()
            self.count = count
     
        def run_item(self):
            if not self._abort:
                subprocess.call ('python -c "import time; time.sleep(0.5)"')
     
        def run(self):
     
            step = 100 / self.count
            done = 0
     
            def callback(future):
                nonlocal done
                if not self._abort:
                    done += step
                    print('callback', done)
                    self.on_update.emit(done)
                    if done >= 100 :
                        self.on_done.emit()
     
     
            self.futures = []
            pool = self.pool = Pool(max_workers=2)
     
            for z in range(self.count):
                future = pool.submit(self.run_item)
                future.add_done_callback(callback)
                self.futures.append(future)
     
        def shutdown(self):  # ** too much **
            self._abort = True
            for f in self.futures:
                f.cancel()
            self.pool.shutdown(wait=True)
     
     
     
    if __name__ == '__main__':
        from PyQt5.QtWidgets import QApplication, QProgressDialog, QPushButton
     
        def do_launch():
            launcher = Launcher()
            dialog = QProgressDialog("En cours...", "Annuler", 0, 100)
            launcher.on_update.connect(dialog.setValue)
            launcher.on_done.connect(dialog.reset)
            launcher.start()  
     
            dialog.exec_()   
            launcher.shutdown()
     
     
        app = QApplication([])
     
        btn = QPushButton('start')
        btn.clicked.connect(do_launch)
        btn.show()
     
        exit(app.exec_())
    fonctionnellement, çà fait la même chose que la mouture précédente excepté l'ajout d'un arrêt/shutdown pour gérer l'option qui va avec dans la QProgressDialog.

    Citation Envoyé par tyrtamos Voir le message
    Un thread utilisant QThread est nécessaire pour qu'il soit capable d'envoyer (.emit(...)) des messages au graphique pour, par exemple, mettre à jour une barre de progression.

    Mais il arrive qu'on préfère utiliser threading de Python pour le thread. Si c'est le cas, il est possible de lui faire émettre quand même des messages au graphique PyQt de la façon suivante: on utilise une classe qui hérite en même temps de threading et QObject (héritage multiple)!
    Dans ce bazar, on peut choisir la mouture du thread principal mais pas celles utilisées par concurrent.future (qui sont des threads standards Python). Donc on peut faire simple.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  17. #17
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    Merci beaucoup à vous deux !

    mes tests fonctionnent, il ne me reste plus qu'à intégrer tout ça dans mon soft
    Sous Kubuntu 20.04

  18. #18
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    Bonjour à vous,

    je réouvre ce topic car j'ai une question annexe.

    Si je lance mon soft, le QThread puis le multiprocess et que je veux le kill le tout depuis la console avec "ctrl + c", ça ne fait que killer l'un des multiple process.

    Avez vous une idée pour résoudre le problème ?

    J'ai pensé à pas mal de trucs mais ça ne le fait pas trop, genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    except KeyboardInterrupt:
    bien que je l'ai mis à différents endroits, je n'ai pas réussis à déclencher cet except.

    Est-ce possible ? et si oui, où faut il le placer ?
    Une autre idée ou solution ?

    Merci d'avance à vous !
    Sous Kubuntu 20.04

  19. #19
    Expert éminent

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    Par défaut
    Salut,

    Si CTRL+C interrompt le programme c'est en général avec un traceback qui t'indique où, dans le code, le signal a été intercepté.

    De toutes façons c'est dans la boucle principale, pas dans un thread.


    Autre chose, tu as vu ceci:
    https://pypi.python.org/pypi/pytesseract
    https://pypi.python.org/pypi/tesserocr

  20. #20
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Points : 460
    Points
    460
    Par défaut
    En fait les traceback ne sont pas toujours au même endroit...
    j'ai essayé à divers endroits mais pas trouvé la soluce...

    Ce que je propose diffère de ce qui existe déjà (du moins je m'en convaincs :p )


    Il me dit que ça vient de ma fonction qui gere la progression de la progressbar
    La fonction contient :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
            ### Envoie de la valeur
            self.ProgressBar.setValue(value)
     
     
            ### Force la mise à jour graphique
            QCoreApplication.processEvents()
    j'ai essayé d'y mettre le try et l'except KeyboardInterrupt
    ...
    Sous Kubuntu 20.04

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. MPTL: MultiProcessing Template Library
    Par alskaar dans le forum Langage
    Réponses: 16
    Dernier message: 29/04/2007, 16h44
  2. Réponses: 8
    Dernier message: 21/12/2006, 21h03
  3. Langage pour contrôle multiprocessing
    Par crocodile dans le forum Langages de programmation
    Réponses: 4
    Dernier message: 13/06/2006, 20h02
  4. lock de fichier - multiprocess
    Par hugo123 dans le forum Entrée/Sortie
    Réponses: 5
    Dernier message: 11/04/2006, 10h08
  5. Réponses: 16
    Dernier message: 30/01/2004, 11h05

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo