[Python] Applicazione multithreading

Daniele Varrazzo piro a develer.com
Lun 19 Mar 2007 12:04:20 CET


> Salve a tutta la lista,
> una di queste sere mi sono imbattuto nel visualizzare il codice sorgente
> di qualche applicazione, e dopo 30 secondi ho deciso di scrivermi già il
> mio primo programmino.
> Bello anzi bellissimo a dir poco stupendo.
> In 11 righe mi sono evitato un mare di lavoro.
> Ho deciso di creare una nuova applicazione e questa volta ho deciso di
> creare uno spider che avevo fatto in c# in windows e rifarlo in python e
> devo dire che tutto funziona alla grande.
> Il mio unico problema sta nel fatto che vorrei velocizzare il programma
> e quindi renderlo multithreading impostando sempre da riga di comando il
> numero di thread che vorrei fare. Mi potete dare qualche consiglio???
>
> Grazie a tutti per il lavoro che fate.

Ciao,

Come già spiegato anche da Valentino, non sempre "multithreading" implica
"più veloce". Ma nel tuo caso è così, caschi bene :)

In fondo al messaggio attacco uno script che avevo scritto un po' di tempo
fa: usa diversi thread per scaricare una serie di file. Non è uno spider
perché la lista di cose da scaricare va specificata all'inizio, ma penso sia
chiara la modifica da effettuare: quando un'istanza di Job trova una url
"interessante", deve aggiungerla alla lista "DownloadManager.urls" dei
compiti da svolgere. Dovresti modificare la signature di Job.__init__ in
modo che possa ricevere un puntatore a tutto il download manager (che nome
borioso!) anziché al solo callback che notifica dell'avvenuto download: io
aggiungerei un metodo DownloadManager.add_job(...) che aggiunga il nuovo job
alla lista rispettando il lock.

"from util import get_logger" non funzionarà: usa "import logging;
logging.getLogger" al suo posto. Tutto il resto dovrebbe funzionare.

Spero ti serva di "ispirazione": ci ho messo pochi minuti a scriverlo, ha
fatto il suo porco lavoro e in effetti il fatto che in Python si scriva un
download manager multi-thread in meno di 100 righe fa la sua figura :)

Divertiti!

-- 
Daniele Varrazzo - Develer S.r.l.
http://www.develer.com

-------8<-------8<-------8<-------8<-------
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""Download a bunch of pdb files taken from pdblist.txt
"""

# Copyright (C) 2005 by Daniele Varrazzo
# $Id: dllib.py 909 2005-05-06 01:05:53Z Daniele $
__version__ = "$Revision: 909 $"[11:-2]

import os
import sys
import threading
from sets import Set
from urllib2 import urlopen

from util import get_logger

logger = get_logger("download")

class DownloadManager:
    """Download a set of url contents into local files."""
    MAX_THREADS = 10
    lock = threading.Lock()

    def __init__(self, urls):
        """Create a new `DownloadManager`

        :Parameters:
          * `urls` (`list` of 2-`tuples`): each couple (`url`, `file`) will
            become an asynchronous job and run.

        """
        self.urls = urls

        logger.info("%d files to download" % len(self.urls))

        for i in range(min(self.MAX_THREADS, len(self.urls))):
            url, filename = self.urls.pop()
            j = Job(url, filename, self.on_finish)
            j.start()

    def on_finish(self, job):
        self.lock.acquire()
        if self.urls:
            logger.info("%d files to go" % len(self.urls))
            url, filename = self.urls.pop()
            j = Job(url, filename, self.on_finish)
            j.start()

        self.lock.release()

class Job(threading.Thread):
    """The download of an url's contents.

    A failed download will be tried `TRYS` times before giving up.
    """
    TRYS = 3
    def __init__(self, url, filename, callback):
        """Create a new `Job`.
        :Parameters:
          * `url` (`str`): the url to be retrieved
          * `filename` (`str`): the file name to be stored
          * `callback` (callable): the function to be called when the job is
            finished. The callback signature is ``callback(job)`` where `job`
            is the caler `Job` instance.
        """
        threading.Thread.__init__(self)
        self.callback = callback
        self.url = url
        self.filename = filename

        logger.info("%s: job created" % url)

    def run(self):
        ok = False
        filename = self.filename
        url = self.url

        for itry in range(self.TRYS):
            try:
                fi = urlopen(url)
                logger.info("%s: url found" % url)
                try:
                    fo = open(filename, "wb")
                    try:
                        while 1:
                            buffer = fi.read(8192)
                            if not buffer:
                                break
                            fo.write(buffer)
                    finally:
                        fo.close()
                finally:
                    fi.close()

            except Exception, exc:
                logger.warning("%s: error opening: %s" % (url, exc))
                # Remove partially downloaded files
                if os.path.exists(filename):
                    os.remove(filename)

            else: # Downloaded: don't try further
                ok = True
                break

        if ok:
            logger.info("%s: download OK" % (url))
        else:
            logger.warning("%s: download FAILED" % (url))

        self.callback(self)



Maggiori informazioni sulla lista Python