Python 3 - 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 sono talvolta 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.

Esistono due diversi tipi di thread:

  • thread del kernel
  • thread utente

I thread del kernel fanno parte del sistema operativo, mentre i thread dello spazio utente non sono implementati nel kernel.

Ci sono due moduli che supportano l'uso dei thread in Python3 -

  • _thread
  • threading

Il modulo thread è stato "deprecato" per parecchio tempo. Gli utenti sono incoraggiati a utilizzare invece il modulo di threading. Quindi, in Python 3, il modulo "thread" non è più disponibile. Tuttavia, è stato rinominato in "_thread" per compatibilità all'indietro in Python3.

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/python3

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

Produzione

Quando il codice sopra viene eseguito, produce il seguente risultato:

Thread-1: Fri Feb 19 09:41:39 2016
Thread-2: Fri Feb 19 09:41:41 2016
Thread-1: Fri Feb 19 09:41:41 2016
Thread-1: Fri Feb 19 09:41:43 2016
Thread-2: Fri Feb 19 09:41:45 2016
Thread-1: Fri Feb 19 09:41:45 2016
Thread-1: Fri Feb 19 09:41:47 2016
Thread-2: Fri Feb 19 09:41:49 2016
Thread-2: Fri Feb 19 09:41:53 2016

Il programma va in un ciclo infinito. Dovrai premere ctrl-c per fermarti

Sebbene sia molto efficace per il threading di basso livello, il modulo thread è molto limitato rispetto al modulo 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/python3

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, self.counter, 5)
      print ("Exiting " + self.name)

def print_time(threadName, delay, counter):
   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()
thread1.join()
thread2.join()
print ("Exiting Main Thread")

Risultato

Quando eseguiamo il programma sopra, produce il seguente risultato:

Starting Thread-1
Starting Thread-2
Thread-1: Fri Feb 19 10:00:21 2016
Thread-2: Fri Feb 19 10:00:22 2016
Thread-1: Fri Feb 19 10:00:22 2016
Thread-1: Fri Feb 19 10:00:23 2016
Thread-2: Fri Feb 19 10:00:24 2016
Thread-1: Fri Feb 19 10:00:24 2016
Thread-1: Fri Feb 19 10:00:25 2016
Exiting Thread-1
Thread-2: Fri Feb 19 10:00:26 2016
Thread-2: Fri Feb 19 10:00:28 2016
Thread-2: Fri Feb 19 10:00:30 2016
Exiting Thread-2
Exiting Main Thread

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/python3

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")

Produzione

Quando il codice sopra viene eseguito, produce il seguente risultato:

Starting Thread-1
Starting Thread-2
Thread-1: Fri Feb 19 10:04:14 2016
Thread-1: Fri Feb 19 10:04:15 2016
Thread-1: Fri Feb 19 10:04:16 2016
Thread-2: Fri Feb 19 10:04:18 2016
Thread-2: Fri Feb 19 10:04:20 2016
Thread-2: Fri Feb 19 10:04:22 2016
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/python3

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")

Produzione

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