Come usare il threading in Python?

Sto cercando di capire il threading in Python. Ho guardato la documentazione e gli esempi, ma francamente, molti esempi sono troppo sofisticati e sto avendo problemi a capirli.

Come si fa a mostrare chiaramente che i compiti vengono divisi per il multi-threading?

Ecco un semplice esempio: bisogna provare alcuni URL alternativi e restituire il contenuto del primo che risponde.

import Queue
import threading
import urllib2

# called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Questo è un caso in cui il threading è usato come una semplice ottimizzazione: ogni subthread è in attesa che un URL si risolva e risponda, in modo da mettere il suo contenuto nella coda; ogni thread è un demone (non manterrà il processo in piedi se il thread principale termina -- è più comune che non); il thread principale avvia tutti i subthread, fa un get sulla coda per aspettare che uno di loro abbia fatto un put, poi emette i risultati e termina (il che porta giù qualsiasi subthread che potrebbe essere ancora in esecuzione, poiché sono thread demone).

L'uso corretto dei thread in Python è invariabilmente collegato alle operazioni di I/O (dato che CPython non usa comunque core multipli per eseguire compiti legati alla CPU, l'unica ragione per il threading è non bloccare il processo mentre c'è un'attesa per qualche I/O). Le code sono quasi invariabilmente il modo migliore per distribuire il lavoro ai thread e/o raccogliere i risultati del lavoro, a proposito, e sono intrinsecamente threadsafe quindi vi salvano dal preoccuparvi di lock, condizioni, eventi, semafori, e altri concetti di coordinazione/comunicazione inter-thread.

Commentari (14)

NOTANOTA: Per l'effettiva parallelizzazione in Python, dovreste usare il modulo multiprocessing per biforcare processi multipli che eseguono in parallelo (a causa del blocco globale dell'interprete, i thread di Python forniscono l'interleaving ma sono di fatto eseguiti in serie, non in parallelo, e sono utili solo quando si interlacciano operazioni di I/O).

Comunque, se state semplicemente cercando l'interleaving (o state facendo operazioni di I/O che possono essere parallelizzate nonostante il blocco globale dell'interprete), allora il modulo threading è il posto dove iniziare. Come esempio molto semplice, consideriamo il problema di sommare un grande intervallo sommando in parallelo i sottogruppi:

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i

thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()  
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Notate che quanto sopra è un esempio molto stupido, in quanto non fa assolutamente I/O e sarà eseguito in serie anche se interleaved (con l'overhead aggiunto del context switching) in CPython a causa del blocco globale dell'interprete.

Commentari (8)

Come altri hanno menzionato, CPython può usare i thread solo per le attese I\O a causa del GIL. Se volete beneficiare di più core per compiti legati alla CPU, usate multiprocessing:

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
Commentari (5)