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 :

Optimisation code, exemple de gestion des threads [Python 3.X]


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre habitué
    Homme Profil pro
    Technicien maintenance
    Inscrit en
    Février 2013
    Messages
    8
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Suisse

    Informations professionnelles :
    Activité : Technicien maintenance
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2013
    Messages : 8
    Par défaut Optimisation code, exemple de gestion des threads
    Bonjour à toutes et tous,

    J'ai testé un exemple de programmation parallèle donné dans le magazine GNU/Linux Magazine Hors-Série n.73 dans le but d'apprendre, voici 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
    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
    70
    71
    72
    73
     
    from threading import Thread
    from queue import Queue
    from timeit import timeit
    import requests
    from bs4 import BeautifulSoup #https://www.crummy.com/software/BeautifulSoup/
    import urllib.request
     
    NAME_NUMBER = 1
     
    def sequential(images_uri):
        for image_uri in images_uri:
            print('\rDownload of %s' % image_uri)
            download(image_uri)
        print('\rDownload complete')
        print('-' * 60)
     
    def parallel(images_uri):
        class Worker(Thread): 
            def __init__(self, queue, image_uri):
                self.queue = queue
                self.image_uri = image_uri
                Thread.__init__(self)
     
            def run(self):
                print('\rDownload of %s' % self.image_uri)
                download(self.image_uri)
                self.queue.task_done() #reporting that the job is finished
     
        #start tasks
        try:
            q = Queue(len(images_uri))
            for image_uri in images_uri:
                task = Worker(q, image_uri)
                task.start()
                q.put(task)
                print('\rWaiting for the download to finish')
                q.join() # Waiting for the last task to finish
            print('\rDownload complete ')
        except:
            print("Error, unable to start the tasks")
        print('-' * 60)
     
    def get_image_uri(url):
        r = requests.get(url)
        data = r.text
        soup = BeautifulSoup(data, "lxml")
     
        imagelink = []
        images = []
     
        for link in soup.find_all('img'):
            imagelink.append(link.get("src"))
     
        for imagelist in imagelink:
            if imagelist.startswith('http://dam'):
                images.append(imagelist)
     
        return images
     
    def download(images_uri):
        global NAME_NUMBER
        full_file_name = str(NAME_NUMBER) + '.png'
        urllib.request.urlretrieve(images_uri,full_file_name)
        NAME_NUMBER += 1
     
    images_uri = get_image_uri('http://www.formation-python.com/')
     
    print('--- Starting sequential download ---')
    print(timeit('sequential(images_uri)', number=1, setup="from __main__ import sequential, images_uri"))
    print()
    print('--- Starting parallel download ---')
    print(timeit('parallel(images_uri)', number=1, setup="from __main__ import parallel, images_uri"))
    Les fonctions download et get_image_uri n'ont pas étées données dans l'exemple du magazine et je les ai construites en cherchant les solutions sur le web. Mais je ne suis pas sûr de leur exactitude. J'aimerai avoir un code correcte où je puisse par la suite me référencer.

    Merci d'avance pour votre analyse d'expert.

  2. #2
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    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 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Pour que le programme soit exécuté, il faut ajouter avant les modules externes suivants: requests, BeautifulSoup4, lxml. Après, le programme fonctionne correctement, et les téléchargements se font bien.

    Cependant, le code me semble particulièrement inefficace, puisque lorsqu'un thread est lancé, on attend qu'il soit terminé pour lancer le suivant. Ce qui fait que le temps total de téléchargement correspond en gros à la somme des téléchargements individuels. Autant se passer des threads...

    Voilà une autre solution plus simple et plus rapide, mais je vais supposer que nous avons déjà obtenu la liste des adresses des images à télécharger.

    On va créer autant de threads qu'il y a de fichiers à télécharger, et on va les lancer tous en même temps (start). Il vont donc s'exécuter tous en parallèle. Et on va attendre (join) qu'ils soient tous terminés pour continuer.

    La partie thread est ici tellement simple qu'il n'est pas utile de créer une classe.

    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
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    # Python 3.5
     
    from threading import Thread
    import urllib.request
    from time import clock
     
    ##############################################################################
    def worker(image_uri, full_file_name):
        print('Download of %s' % image_uri)
        urllib.request.urlretrieve(image_uri, full_file_name)
     
    ##############################################################################
    if __name__ == '__main__':
     
        images_uri = [
        "http://dam.inspyration.fr/inspyration/py.png",
        "http://dam.inspyration.fr/inspyration/np.png",
        "http://dam.inspyration.fr/inspyration/od.png",
        "http://dam.inspyration.fr/inspyration/wb.png"
        ]
     
        try:
            tasks = []
            t = clock()
            # on va lancer autant de thread qu'il y a d'images à télécharger
            for image_uri in images_uri:
                full_file_name = image_uri.split('/')[-1]
                tasks.append(Thread(target=worker, args=(image_uri, full_file_name)))
                tasks[-1].start()
            # ici, tous les threads ont été lancés
            for task in tasks:
                task.join() 
            # ici, tous les threads sont terminés
            t = clock()-t
            print('Download complete (', t, 's)')
        except:
            print("Error, unable to start the tasks")
        print('-' * 60)
    Ce qui télécharge tous les fichiers en... 0.15s, soit 6 à 7 fois plus rapidement que ton code.

    Bien sûr, créer 4 threads pour télécharger 4 fichiers n'est pas un problème. Mais s'il fallait en télécharger 1000, ce ne serait pas raisonnable de créer 1000 threads en même temps. On pourrait alors changer le code de la façon suivante:
    - placer les 1000 url des fichiers à télécharger dans une queue "in"
    - créer une queue "out" vide pour les résultats
    - créer par exemple 10 threads en leur passant les 2 queues "in" et "out" comme arguments
    - chaque thread s'auto-alimente en travail en testant dans une boucle while s'il y a encore des fichiers à télécharger (queue "in" non vide) et si oui, en retire un de la queue, le télécharge et informe de la fin dans la queue "out". Si la queue "in" est vide, le thread peut se saborder lui-même (=se terminer en sortant de sa boucle while).
    - la fin de tous les téléchargement est obtenue quand la taille de la queue "out" est la même que le nombre de fichiers à télécharger.

    Dans la mesure où les téléchargements sont des tâches indépendantes, on peut aussi utiliser le module "concurrent.futures" afin d'utiliser tous les "cores" disponibles de nos CPU "multicores" actuels. Ce serait la solution la plus rapide qui exploiterait au maximum la puissance du PC concerné. Exemple:

    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
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    # Python 3.5
     
    import urllib.request
    from time import clock
    from concurrent.futures import ThreadPoolExecutor
     
    ##############################################################################
    def worker(image_uri):
        print('Download of %s' % image_uri)
        full_file_name = image_uri.split('/')[-1]
        urllib.request.urlretrieve(image_uri, full_file_name)
        return full_file_name
     
    ##############################################################################
    if __name__ == '__main__':
     
        images_uri = [
        "http://dam.inspyration.fr/inspyration/py.png",
        "http://dam.inspyration.fr/inspyration/np.png",
        "http://dam.inspyration.fr/inspyration/od.png",
        "http://dam.inspyration.fr/inspyration/wb.png"
        ]
     
        # exécution des travaux et enregistrement des résultats dans R
        R = []
        t = clock()
        with ThreadPoolExecutor() as executor:
            for travail, resultat in zip(images_uri, executor.map(worker, images_uri)):
                R.append(resultat)
        t = clock()-t
     
        print(R)
        print(t)
    Ici, le temps obtenu est du même ordre de grandeur que précédemment (env. 0.15s), probablement parce que le nombre de fichiers à télécharger est trop faible ici pour faire apparaître une différence.

  3. #3
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    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 486
    Billets dans le blog
    6
    Par défaut
    Juste un petit complément à mon message précédent.

    J'avais dit que pour télécharger 1000 fichiers, il ne fallait pas créer 1000 threads. J'avais alors décrit le principe d'une solution consistant à créer un nombre limité de threads permanents, qui iront chercher eux-même le travail à faire, tant qu'il y en a encore. Voilà un petit exemple de 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
    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
    70
    71
    72
    73
    74
    75
    76
    77
    78
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    # Python 3.5
     
    from threading import Thread
    import urllib.request
    from time import clock
    from queue import Queue
     
    ##############################################################################
    class Worker(Thread):
     
        def __init__(self, qin, qout):
            super().__init__()
            self.qin = qin
            self.qout = qout
            self.encore = True
     
        def run(self):
            while self.encore:
                if not self.qin.empty(): # => pile d'entrée non vide
                    image_uri = self.qin.get()
                    full_file_name = image_uri.split('/')[-1]
                    urllib.request.urlretrieve(image_uri, full_file_name)
                    self.qout.put(full_file_name)
     
        def stop(self):
            self.encore = False
     
    ##############################################################################
    if __name__ == '__main__':
     
        images_uri = [
        "http://dam.inspyration.fr/inspyration/py.png",
        "http://dam.inspyration.fr/inspyration/np.png",
        "http://dam.inspyration.fr/inspyration/od.png",
        "http://dam.inspyration.fr/inspyration/wb.png"
        ]
     
        lg = len(images_uri)
        nbtasks = 10
     
        # crée les 2 queues "in" et "out"
        qin = Queue()
        qout = Queue()
     
        # met en place les threads "permanents"
        tasks = []
        for i in range(0, nbtasks):
            tasks.append(Worker(qin, qout))
     
        t = clock()
     
        # remplit la pile de travail avec tous les fichiers à télécharger 
        for image_uri in images_uri:
            qin.put(image_uri)
     
        t = clock()
     
        # lance les threads
        for task in tasks:
            task.start()
     
        # attend que la totalité des résultats soit obtenue
        while qout.qsize()<lg:
            pass 
     
        t = clock()-t
     
        # supprime tous les threads
        for task in tasks:
            task.stop()
     
        # affiche les résultats
        R = [qout.get() for i in range(0, lg)]
        print(R)
        print('Download complete (', t, 's)')
        print('-' * 60)
    L'application au téléchargement des 4 fichiers n'est pas adapté au code (résultat de l'ordre de 0.40s). Il faudrait essayer avec plusieurs dizaines de fichiers à télécharger. En tout cas, c'est très amusant à coder

  4. #4
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 741
    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 741
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    L'application au téléchargement des 4 fichiers n'est pas adapté au code (résultat de l'ordre de 0.40s). Il faudrait essayer avec plusieurs dizaines de fichiers à télécharger. En tout cas, c'est très amusant à coder
    Pourquoi espérer que ce soit plus rapide que la bande passante de votre connexion réseau et/ou de celle du serveur qui retourne les données?
    Essayez de traiter des fichiers disques (locaux les disques), çà supprimerait nombre d'aléa permettant d'avoir une différence (ok, la bande passante disque, puis celle du CPU et enfin celle de la mémoire).

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

  5. #5
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    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 486
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks,

    Je ne faisais que répondre à la question posée qui porte sur le téléchargement de plusieurs fichiers avec des threads. Il est évident qu’on ne peut pas dépasser le débit maxi du réseau, mais selon le codage et les fichiers concernés, ce débit peut ne pas être atteint.

  6. #6
    Membre habitué
    Homme Profil pro
    Technicien maintenance
    Inscrit en
    Février 2013
    Messages
    8
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Suisse

    Informations professionnelles :
    Activité : Technicien maintenance
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2013
    Messages : 8
    Par défaut
    Merci pour vos réponses et conseils!

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. gestion des threads
    Par yanis97 dans le forum Langage
    Réponses: 6
    Dernier message: 20/04/2006, 12h41
  2. Gestion des Threads
    Par Nalfouille dans le forum MFC
    Réponses: 3
    Dernier message: 05/04/2006, 16h29
  3. Gestion des threads
    Par yanis97 dans le forum C++
    Réponses: 6
    Dernier message: 08/03/2006, 09h39
  4. GEstion des thread
    Par Julien Dufour dans le forum Access
    Réponses: 8
    Dernier message: 06/10/2004, 14h28
  5. [reseaux] Gestion des threads en perl
    Par totox17 dans le forum Programmation et administration système
    Réponses: 2
    Dernier message: 28/11/2002, 09h40

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