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.
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.
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()