[Python] Applicazione multithreading

Daniele Varrazzo piro a develer.com
Lun 19 Mar 2007 13:36:42 CET


> On Mon, 19 Mar 2007 12:04:20 +0100 (CET), Daniele Varrazzo
> <piro a develer.com> wrote:
>>Come già spiegato anche da Valentino, non sempre "multithreading" implica
>>"più veloce". Ma nel tuo caso è così, caschi bene :)
>
> Beh no, dissento profondamente.

Allora, de-dissenti, questa volta :) Se non ci credi, metti 1000 url in
questa lista, e lanciala con MAX_THREAD prima a 1, poi a 10. Vedrai che c'è
differenza.

La differenza c'è perché la cosa più lenta è la rete, che nel mio caso
poteva essere sfruttata meglio. Avevo 1000 file da scaricare e mentre andava
un file alla volta vedevo col firewall personale che non facevo più di (boh,
metti) 10k/sec. Con la versione multithread ho sfruttato tutta la banda
disponibile e ho finito in una frazione del tempo (minuti anziché ore).

> Quantomeno e` veloce uguale, in python e`
> solo piu` lento.

Se eseguisse bytecode di continuo sarebbe così. Ma il collo di bottiglia è
il read, che probabilmente è una chiamata in C che rilascia il GIL.

> Secondariamente in questo caso usi un quantitativo di ram proibitivo

dov'è la ram?

per
> fare poco o nulla e il
> codice risultante e` incredibilmente piu` complesso di quelle 20 righe che
> ho scritto che facilmente si possono riadattare a fare il crawler.

Quelle si appoggiano su twisted: a quelle 20 righe devi aggiungere
l'imparare twisted, che non è esattamente una passeggiata. Per te è ovvio
perché probabilmente mangi pane e twisted, ma non puoi dire che sia facile
ad una persona che sta imparando il Python ora.

>>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.
>
> Cosi` a naso direi che il tuo codice contiene almeno una race condition che
> solo casualmente
> non hai mai incontrato nella vita reale.
>
> Infatti il loop nell'__init__ del download manager usa: url, filename =
> self.urls.pop()
> senza pero` acquisire e rilasciare il lock su urls. Questo significa che se
> un download
> finisce prima che possa terminare questo loop abbiamo almeno due risorse che
> accedono
> contemporaneamente a quella variabile e chissa` cosa succede :). Sistemarlo
> e` banale,
> tuttavia in 100 righe di codice molto semplice e con un solo lock da gestire
> c'e` stato
> spazio per una race condition, cosi` facile non deve essere.

Ci può essere, perché come detto è uno scriptino usa e getta che doveva fare
un lavoro una volta e finire lì. Fai bene a puntare il dito verso il
probabile bug, almeno insegni le rogne di scrivere in multithread.

mi prequoto:

>>Spero ti serva di "ispirazione": ci ho messo pochi minuti a scriverlo, ha
>>fatto il suo porco lavoro

speravo fosse superfluo dire anche "non ho voglia di difendere con le unghie
e con i denti un lavoro estemporaneo fatto per superare un problema
estemporaneo ed esplicitamente proposto come tale".

>>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 :)
>
> Se togliamo la race condition, forse.
>
> E` proprio una questione architetturale: con il multi-threading l'esecuzione
> e` al massimo
> veloce uguale e in rari casi e neanche troppo frequenti.

Te l'ho detto: non è stata ottimizzazione preventiva. Quello che dici tu è
vero fintantoché resti nel bytecode. Ho scritto un programma con codice
analogo che effettua interrogazioni ad un db ospitato su una macchina
multiprocessore e con un disco molto veloce. Risultato: con i thread vengono
processate 8 query contemporaneamente e il tempo totale di processo è pari
al tempo della più lenta delle query (circa 60 sec) più 1 sec, mentre prima
era la somma dei tempi. Un incremento di velocità di circa il 500%.

Grazie della discussione: penso sia stata più utile di come hai trattato il
povero malcapitato in precedenza. Ha sentito parlare di thread, message
passing, state sharing, lock, race condition, twisted. Ha visto differenze
di pensiero.

Anche io sono piuttosto orso il grosso delle volte, però mi sforzo di non
mangiare vive le persone solo perché ne sanno meno di me. :) Non stiamo
parlando giusto ora di organizzare un evento finalizzato a far avvicinare
persone al Python? Penso che dovremo rivolgerci positivamente ai neofiti se
vogliamo che non fuggano terrorizzati ;)

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


Maggiori informazioni sulla lista Python