Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


multithreading in Python - Semafori, Appunti di Informatica

Ottima lezione didattica per introdurre i semafori in python. Esempi e spiegazione in italiano

Tipologia: Appunti

2019/2020

Caricato il 25/02/2020

gabryunipegaso
gabryunipegaso 🇮🇹

4

(1)

5 documenti

1 / 9

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
admin 2 Gennaio
2020
Multithreading in Python #4 – I semafori
ildatocheconta.com/multithreading-in-python-4-i-semafori/
In questa quarta lezione vediamo cosa sono i semafori e come possono risolvere i
problemi di mutua esclusione e di sincronizzazione.
Ricordiamo brevemente che per mutua esclusione si intende che non più di un thread
alla volta può accedere ad una risorsa comune. Si definisce sincronizzazione come una
procedura che assicura che due o più thread concorrenti non eseguano in maniera
simultanea parti di codici che accedono a risorse condivise. Inoltre si ricorda che due o
più thread si dicono concorrenti se la prima istruzione di uno di essi ha inizio prima che
si concluda l’ultima istruzione di un altro di esso.
Ricordiamo brevemente che
A questo punto definiamo un semaforo come un oggetto che è utile per gestire i
meccanismi di collaborazione e competizione tra i vari thread. (Per competizione si
intende una interazione tra i due thread prevedibile e non desiderata, mentre per
collaborazione si intende una interazione prevedibile e desiderata, cioè regolata
direttamente dal programma. Per interferenza si intende una interazione tra due thread
non prevedibile e non desiderata).
Questi oggetti sono nati affinché i vari thread possano mandarsi dei segnali. Se un thread
invia un segnale allora si utilizza il metodo signal(), se invece si mettono in attesa per
ricevere un segnale allora si utilizza il metodo wait(). Lo scopo di utilizzare un semaforo è
quello di evitare i cicli di attesa attiva oppure di estendere l’oggetto lock() che è utile solo
in caso di interazione tra due thread.
Ricapitolando, il metodo signal(): il thread invia un segnale all’oggetto semaforo che lo
riceve e lo memorizza
Il metodo wait(): mette in attesa il thread e serve per ricevere i segnali inviati a un dato
semaforo, se il segnale non è stato ancora ricevuto allora il thread si mette in stato di
waiting finché non arriva il segnale; nel momento in cui il segnale è stato ricevuto, il
thread può riprendere la sua esecuzione.
1/9
pf3
pf4
pf5
pf8
pf9

Anteprima parziale del testo

Scarica multithreading in Python - Semafori e più Appunti in PDF di Informatica solo su Docsity!

admin 2 Gennaio 2020

Multithreading in Python #4 – I semafori

ildatocheconta.com /multithreading-in-python-4-i-semafori/ In questa quarta lezione vediamo cosa sono i semafori e come possono risolvere i problemi di mutua esclusione e di sincronizzazione. Ricordiamo brevemente che per mutua esclusione si intende che non più di un thread alla volta può accedere ad una risorsa comune. Si definisce sincronizzazione come una procedura che assicura che due o più thread concorrenti non eseguano in maniera simultanea parti di codici che accedono a risorse condivise. Inoltre si ricorda che due o più thread si dicono concorrenti se la prima istruzione di uno di essi ha inizio prima che si concluda l’ultima istruzione di un altro di esso. Ricordiamo brevemente che A questo punto definiamo un semaforo come un oggetto che è utile per gestire i meccanismi di collaborazione e competizione tra i vari thread. (Per competizione si intende una interazione tra i due thread prevedibile e non desiderata, mentre per collaborazione si intende una interazione prevedibile e desiderata, cioè regolata direttamente dal programma. Per interferenza si intende una interazione tra due thread non prevedibile e non desiderata). Questi oggetti sono nati affinché i vari thread possano mandarsi dei segnali. Se un thread invia un segnale allora si utilizza il metodo signal() , se invece si mettono in attesa per ricevere un segnale allora si utilizza il metodo wait(). Lo scopo di utilizzare un semaforo è quello di evitare i cicli di attesa attiva oppure di estendere l’oggetto lock() che è utile solo in caso di interazione tra due thread. Ricapitolando, il metodo signal() : il thread invia un segnale all’oggetto semaforo che lo riceve e lo memorizza Il metodo wait() : mette in attesa il thread e serve per ricevere i segnali inviati a un dato semaforo, se il segnale non è stato ancora ricevuto allora il thread si mette in stato di waiting finché non arriva il segnale; nel momento in cui il segnale è stato ricevuto, il thread può riprendere la sua esecuzione.

Per quanto riguarda le caratteristiche di un semaforo, esso è composto da una variabile intera(contatore) e da una coda di thread. Per inizializzare un semaforo si assegna un valore alla variabile intera e tale valore può essere modificato solo dai metodi signal() e wait(). La variabile intera serve per contare i segnali inviati al semaforo, il suo valore viene incrementato dal metodo signal() e decrementato dal metodo wait(). La coda dei thread si forma solo nel momento in cui essi cercano di ricevere un segnale tramite il metodo wait(), se la variabile intera (chiamata contatore) è minore o uguale a 0 – quindi non ci sono segnali – allora il processo viene messo in stato di waiting e inserito in coda. I metodi wait() e signal() sono eseguiti in modalità atomica, ossia le istruzioni eseguite da esse sono indivisibili da un punto di vista logico. Ad esempio, nella seguente figura, il semaforo ha il contatore pari a -3 e quindi ci sono in coda 3 thread. L’oggetto semaforo di nome “s” con il contatore s.count pari a -3 e la coda dei thread. La coda è gestita in modalità FIFO e i metodi wait e signal sono eseguiti in modalità atomica (cioè le loro istruzioni sono eseguite in maniera indivisibile da un punto di vista logico, cioè non è possibile interrompere le istruzioni dei metodi signal e wait). Per quanto riguarda il funzionamento dei due metodi, essi vengono eseguiti direttamente dai thread tramite l’istruzione (scritta in pseudocodifica) s.wait() o s.signal(). s.wait() viene invocata da un thread T sul semaforo s, esso decrementa il contatore di un’unità e se diventa minore di 0 allora blocca T e lo inserisce in coda al semaforo s. s.signal() viene invocata da un thread generico T sul semaforo s, esso incrementa di uno il contatore di s e se la coda non è vuota, attiva uno dei thread che si trovavano in stato di waiting nella coda di s. Possiamo quindi definire il contatore come il numero di segnali ricevuti dal semaforo e inviati dai thread non ancora utilizzati con il metodo wait. In generale quindi: Un semaforo è un costrutto utile per la sincronizzazione, fornisce ai thread un accesso sincronizzato a un numero limitato di risorse. Esso può essere definito anche solo come una variabile il cui valore non può mai essere minore di zero o maggiore del numero totale di risorse da condividere. Tale variabile è associato a due operazioni: “acquire” e “release”. Quando una delle risorse sincronizzate da un semaforo è “acquisita” da un thread, allora il valore del semaforo è decrementato, quando invece la risorsa 1 1 x

listaalunni = ['Desiato','Della Valle','De Simone','Fusco','Passariello','Pascarella','Petito','Quagliero','Sagnelli','Varvo'] penna = threading.Semaphore(4) listathreads=[] start=time.perf_counter() penna=threading.Semaphore(4) def signal(): penna.release() def wait(): penna.acquire() def alunno(nome): print("Salve sono {0}. Posso fare il compito?".format(nome)) wait() print("Grazie, ora faccio il compito") time.sleep(random.randint(0, 10)) print("Io {0} ho finito il compito, restituisco la penna".format(nome)) signal() for i in listaalunni: #from 1 to 10 (ten times) t=threading.Thread(target=alunno, args=[i]) t.start() lista_threads.append(t) for thread in lista_threads: thread.join() finish=time.perf_counter() print(finish) print("Algoritmo concluso in %s secondi" %(round(finish-start,2)))

In questo script abbiamo un numero di risorse pari a 4 mentre il numero di alunni è pari a 10. Si lascia all’alunno la spiegazione dello script. SINCRONIZZAZIONE CON I SEMAFORI Vediamo ora un esempio di sincronizzazione tramite l’oggetto semaforo

Abbiamo due treni che devono passare nella stessa stazione, un treno è un regionale mentre l’altro è un treno ad alta velocità. Quello ad alta velocità è più veloce (ciò viene simulato tramite la funzione time.sleep(0.01)), mentre quello regionale più lento. All’inizio dello script quello regionale si trova fermo alla stazione e deve lasciare il binario vuoto all’altro treno. L’altro treno ad alta velocità non può arrivare nella stazione di arrivo finché quello regionale non parta, per tale motivo deve aspettare che gli venga dato il permesso dal semaforo. Se provate ad avviare lo script, noterete che il print “treno arrivato” avviene dopo il print “treno 1 andato via”, proprio per simulare che il binario adesso è vuoto. MUTUA ESCLUSIONE CON I SEMAFORI Per la mutua esclusione invece, possiamo fare anche un confronto con l’oggetto lock. Infatti, possiamo affermare che l’oggetto lock è un semaforo di tipo binario, dove il contatore è posto inizialmente a 1. Possiamo vederlo tramite la comparazione di questi due script equivalenti che simulano la mutua esclusione tra due thread, uno con l’oggetto lock e l’altro tramite l’oggetto semaforo: #mutua esclusione con il lock import threading import time #variabile globale x a cui possono accedere più thread x = 0 start=time.perf_counter() lock = threading.Lock() def cosafare(): global x for i in range(10000): lock.acquire() x += lock.release()

for j in range(10): #mutua esclusione con i semafori import threading import time #variabile globale x a cui possono accedere più thread x = 0 start=time.perf_counter() mutex = threading.Semaphore(1) #lock = threading.Lock() def signal(): mutex.release() def wait(): mutex.acquire() def cosafare(): global x for i in range(10000): #lock.acquire() wait() x += signal()

creazione dei thread

t1 = threading.Thread(target=cosafare) t2 = threading.Thread(target=cosafare)

Avvio dei thread

t1.start() t2.start()

Aspetta finché i thread non finiscano di

eseguire #tutte le operazioni t1.join() t2.join() print("Iterazione indice {0}: x = {1}".format(j,x)) x=0 #riporta la variabile globale a 0 per il prossimo ciclo

finish=time.perf_counter() print("Algoritmo concluso in %s secondi" % (round(finish-start,2))) #lock.release()

for j in range(10):

creazione dei thread

t1 = threading.Thread(target=cosafare) t2 = threading.Thread(target=cosafare)

Avvio dei thread

t1.start() t2.start()

Aspetta finché i thread non finiscano di

eseguire #tutte le operazioni t1.join() t2.join() print("Iterazione indice {0}: x = {1}".format(j,x)) x=0 #riporta la variabile globale a 0 per il prossimo ciclo

finish=time.perf_counter() print("Algoritmo concluso in %s secondi" % (round(finish-start,2))) Inoltre possiamo affermare che la riga di codice: mutex = threading.Lock() è uguale alla seguente riga di codice: mutex = threading.Semaphore(1)