[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