Python - Programmazione multithread
L'esecuzione di più thread è simile all'esecuzione di più programmi diversi contemporaneamente, ma con i seguenti vantaggi:
Più thread all'interno di un processo condividono lo stesso spazio dati con il thread principale e possono quindi condividere informazioni o comunicare tra loro più facilmente che se fossero processi separati.
I thread a volte sono chiamati processi leggeri e non richiedono molto overhead di memoria; sono più economici dei processi.
Un thread ha un inizio, una sequenza di esecuzione e una conclusione. Ha un puntatore di istruzioni che tiene traccia di dove è attualmente in esecuzione nel suo contesto.
Può essere anticipato (interrotto)
Può essere temporaneamente messo in attesa (noto anche come sleep) mentre altri thread sono in esecuzione - questo è chiamato yielding.
Avvio di una nuova discussione
Per generare un altro thread, è necessario chiamare il seguente metodo disponibile nel modulo thread :
thread.start_new_thread ( function, args[, kwargs] )
Questa chiamata al metodo consente un modo rapido ed efficiente per creare nuovi thread sia in Linux che in Windows.
La chiamata al metodo ritorna immediatamente e il thread figlio si avvia e chiama la funzione con l'elenco passato di argomenti . Quando la funzione ritorna, il thread termina.
Qui, args è una tupla di argomenti; usa una tupla vuota per chiamare la funzione senza passare alcun argomento. kwargs è un dizionario opzionale di argomenti di parole chiave.
Esempio
#!/usr/bin/python
import thread
import time
# Define a function for the thread
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print "%s: %s" % ( threadName, time.ctime(time.time()) )
# Create two threads as follows
try:
thread.start_new_thread( print_time, ("Thread-1", 2, ) )
thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print "Error: unable to start thread"
while 1:
pass
Quando il codice sopra viene eseguito, produce il seguente risultato:
Thread-1: Thu Jan 22 15:42:17 2009
Thread-1: Thu Jan 22 15:42:19 2009
Thread-2: Thu Jan 22 15:42:19 2009
Thread-1: Thu Jan 22 15:42:21 2009
Thread-2: Thu Jan 22 15:42:23 2009
Thread-1: Thu Jan 22 15:42:23 2009
Thread-1: Thu Jan 22 15:42:25 2009
Thread-2: Thu Jan 22 15:42:27 2009
Thread-2: Thu Jan 22 15:42:31 2009
Thread-2: Thu Jan 22 15:42:35 2009
Sebbene sia molto efficace per il threading di basso livello, il modulo thread è molto limitato rispetto al modulo di threading più recente.
Il modulo di filettatura
Il nuovo modulo di threading incluso con Python 2.4 fornisce un supporto molto più potente e di alto livello per i thread rispetto al modulo thread discusso nella sezione precedente.
Il modulo threading espone tutti i metodi del modulo thread e fornisce alcuni metodi aggiuntivi -
threading.activeCount() - Restituisce il numero di oggetti thread attivi.
threading.currentThread() - Restituisce il numero di oggetti thread nel controllo thread del chiamante.
threading.enumerate() - Restituisce un elenco di tutti gli oggetti thread attualmente attivi.
Oltre ai metodi, il modulo threading ha la classe Thread che implementa il threading. I metodi forniti dalla classe Thread sono i seguenti:
run() - Il metodo run () è il punto di ingresso per un thread.
start() - Il metodo start () avvia un thread chiamando il metodo run.
join([time]) - Il join () attende che i thread terminino.
isAlive() - Il metodo isAlive () controlla se un thread è ancora in esecuzione.
getName() - Il metodo getName () restituisce il nome di un thread.
setName() - Il metodo setName () imposta il nome di un thread.
Creazione di thread utilizzando il modulo di threading
Per implementare un nuovo thread utilizzando il modulo threading, devi fare quanto segue:
Definisci una nuova sottoclasse della classe Thread .
Sostituisci il metodo __init __ (self [, args]) per aggiungere ulteriori argomenti.
Quindi, sovrascrivi il metodo run (self [, args]) per implementare ciò che il thread dovrebbe fare all'avvio.
Dopo aver creato la nuova sottoclasse Thread , è possibile crearne un'istanza e quindi avviare un nuovo thread invocando start () , che a sua volta chiama il metodo run () .
Esempio
#!/usr/bin/python
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print "Starting " + self.name
print_time(self.name, 5, self.counter)
print "Exiting " + self.name
def print_time(threadName, counter, delay):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print "%s: %s" % (threadName, time.ctime(time.time()))
counter -= 1
# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
print "Exiting Main Thread"
Quando il codice sopra viene eseguito, produce il seguente risultato:
Starting Thread-1
Starting Thread-2
Exiting Main Thread
Thread-1: Thu Mar 21 09:10:03 2013
Thread-1: Thu Mar 21 09:10:04 2013
Thread-2: Thu Mar 21 09:10:04 2013
Thread-1: Thu Mar 21 09:10:05 2013
Thread-1: Thu Mar 21 09:10:06 2013
Thread-2: Thu Mar 21 09:10:06 2013
Thread-1: Thu Mar 21 09:10:07 2013
Exiting Thread-1
Thread-2: Thu Mar 21 09:10:08 2013
Thread-2: Thu Mar 21 09:10:10 2013
Thread-2: Thu Mar 21 09:10:12 2013
Exiting Thread-2
Sincronizzazione dei thread
Il modulo di threading fornito con Python include un meccanismo di blocco semplice da implementare che consente di sincronizzare i thread. Viene creato un nuovo blocco chiamando il metodo Lock () , che restituisce il nuovo blocco.
Il metodo di acquisizione (blocco) del nuovo oggetto blocco viene utilizzato per forzare l'esecuzione dei thread in modo sincrono. Il parametro di blocco facoltativo consente di controllare se il thread attende di acquisire il blocco.
Se il blocco è impostato a 0, il thread ritorna immediatamente con un valore 0 se il blocco non può essere acquisito e con un 1 se il blocco è stato acquisito. Se il blocco è impostato su 1, il thread si blocca e attende che il blocco venga rilasciato.
Il metodo release () del nuovo oggetto lock viene utilizzato per rilasciare il lock quando non è più necessario.
Esempio
#!/usr/bin/python
import threading
import time
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print "Starting " + self.name
# Get lock to synchronize threads
threadLock.acquire()
print_time(self.name, self.counter, 3)
# Free lock to release next thread
threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print "%s: %s" % (threadName, time.ctime(time.time()))
counter -= 1
threadLock = threading.Lock()
threads = []
# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
# Add threads to thread list
threads.append(thread1)
threads.append(thread2)
# Wait for all threads to complete
for t in threads:
t.join()
print "Exiting Main Thread"
Quando il codice sopra viene eseguito, produce il seguente risultato:
Starting Thread-1
Starting Thread-2
Thread-1: Thu Mar 21 09:11:28 2013
Thread-1: Thu Mar 21 09:11:29 2013
Thread-1: Thu Mar 21 09:11:30 2013
Thread-2: Thu Mar 21 09:11:32 2013
Thread-2: Thu Mar 21 09:11:34 2013
Thread-2: Thu Mar 21 09:11:36 2013
Exiting Main Thread
Coda prioritaria multithread
Il modulo Coda consente di creare un nuovo oggetto coda che può contenere un numero specifico di elementi. Esistono i seguenti metodi per controllare la coda:
get() - Il get () rimuove e restituisce un elemento dalla coda.
put() - Il put aggiunge un elemento a una coda.
qsize() - Il qsize () restituisce il numero di elementi attualmente in coda.
empty()- Il empty () restituisce True se la coda è vuota; in caso contrario, False.
full()- full () restituisce True se la coda è piena; in caso contrario, False.
Esempio
#!/usr/bin/python
import Queue
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, q):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.q = q
def run(self):
print "Starting " + self.name
process_data(self.name, self.q)
print "Exiting " + self.name
def process_data(threadName, q):
while not exitFlag:
queueLock.acquire()
if not workQueue.empty():
data = q.get()
queueLock.release()
print "%s processing %s" % (threadName, data)
else:
queueLock.release()
time.sleep(1)
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Queue.Queue(10)
threads = []
threadID = 1
# Create new threads
for tName in threadList:
thread = myThread(threadID, tName, workQueue)
thread.start()
threads.append(thread)
threadID += 1
# Fill the queue
queueLock.acquire()
for word in nameList:
workQueue.put(word)
queueLock.release()
# Wait for queue to empty
while not workQueue.empty():
pass
# Notify threads it's time to exit
exitFlag = 1
# Wait for all threads to complete
for t in threads:
t.join()
print "Exiting Main Thread"
Quando il codice sopra viene eseguito, produce il seguente risultato:
Starting Thread-1
Starting Thread-2
Starting Thread-3
Thread-1 processing One
Thread-2 processing Two
Thread-3 processing Three
Thread-1 processing Four
Thread-2 processing Five
Exiting Thread-3
Exiting Thread-1
Exiting Thread-2
Exiting Main Thread