• Non ci sono risultati.

Parte 2: Sincronizzazione dei Processi (completo)

N/A
N/A
Protected

Academic year: 2021

Condividi "Parte 2: Sincronizzazione dei Processi (completo)"

Copied!
24
0
0

Testo completo

(1)

7. Sincronizzazione dei Processi

• Importante: l’accesso concorrente a dati condivisi può produrre dati inconsistenti • processi cooperanti attraverso la condivisione

di dati devono agire in modo ordinato, cioè sincronizzarsi

• Esempio dimostrativo Produttore/Consumatore che usa tutti gli elementi del buffer. (n elementi al massimo nel buffer)

2

Esempio:

Produttore-Consumatore con n elementi

• Aggiungere counter inizializzato a 0 alle variabili shared che indica il numero di elementi nel buffer

shared var n: integer; counter: integer; type item = ...;

shared var buffer: array [0..n-1] of item;

shared var in, out: 0..n-1;

• Buffer circolare con due puntatori in e out inizializzati a 0 (vedi cap. 4)

3

Esempio:

Produttore-Consumatore con n elementi

Produttore

var nextp: item; repeat

<produce un nuovo item in nextp>

while counter = n do no_op; buffer[in] := nextp;

in := in+1 mod n; counter := counter+1;

(2)

Esempio:

Produttore-Consumatore con n elementi

Consumatore

var nextc: item; repeat

while counter = 0 do no_op; nextc := buffer[out]; out := out+1 mod n; counter := counter-1; <consuma l'item in nextc> until false;

5

Esempio:

Produttore-Consumatore con n elementi

• Sebbene le routine siano corrette se considerate separatamente, possono non funzionare se eseguite in concorrenza !!! • Problema: accesso alla variabile condivisa

counter

• Se counter vale 5, dopo le istruzioni di incremento e decremento può essere uno qualunque tra 4, 5 o 6 !!

6

Cosa non funziona ?

• Incremento e decremento di counter In producer: register1 = counter register1 = register1 + 1 counter = register1 In consumer: register2 = counter register2 = register2 - 1 counter = register2

(3)

Cosa non funziona

Producer: register1 = counter Producer: register1 = register1 + 1 Consumer: register2 = counter Consumer: register2 = register2 - 1 Producer: counter = register1 Consumer: counter = register2

8

7.2 Sezioni critiche

• I due programmi possono produrre risultati scorretti perché insieme manipolano la stessa variabile concorrentemente

• Se avessi l’aritmetica in memoria (una sola istruzione, un solo processore):

– nessun problema ⇒ una istruzione è eseguita in modo atomico

9

7.2 Sezioni critiche

• Se avessi l’aritmetica in memoria ma più di un processore:

– ancora problemi perché l’istruzione accede due volte in memoria (per leggere e per depositare il risultato)

• L’intrallacciamento di istruzioni o di accessi in memoria provoca corsa critica

⇒ sequenzializzare l’accesso alla variabile da manipolare concorrentemente

(4)

7.2 Sezioni critiche

• Sezione critica =:= segmento di codice che deve essere eseguito senza interfogliamento con altre sezioni critiche della stessa famiglia • Le variabili condivise determinano le

sezioni critiche.

• Problema: assicurare che quando un processo sta eseguendo la propria sezione critica, nessun altro processo abbia il permesso di eseguire la sua sezione critica.

11

7.2 Sezioni critiche

• Un processo è costituito da:

♦ entry section: per chiedere di entrare ♦ sezione critica vera e propria

♦ exit section per segnalare l’uscita dalla sezione critica

♦ remainder section che non fa accesso a dati critici

12

7.2 Proprietà richieste alle

soluzioni

• Mutua esclusione nella sezione critica • Progresso: se la sezione critica è vuota,

solo chi cerca di entrare partecipa alla decisioni di chi entrerà davvero, ed entro un tempo finito

• Attesa limitata superiormente, per chi attende fuori che la sezione si liberi

(5)

7.2 Proprietà richieste alle

soluzioni

• Velocità dei processi non nulla, ma nessuna ipotesi sulle velocità relative perché la soluzione non dipenda dallo scheduling della CPU

• Vediamo una soluzione per due processi…

– Usiamo una variabile turn inizializzata a 0 – se turn = i, allora Pipuò accedere alla sezione

critica

14

Algoritmo 1

• Il codice di Piè repeat

while (turn ≠ i) do no_op; <critical section>

turn = j;

<remainder section> until false;

• Assicura mutua esclusione, ma non il progresso: stretta alternanza dei processi

15

Algoritmo 2

• Sostituiamo turn con

– shared var flag: array[0..1] of boolean;

– Inizializzazione flag[0] = flag[1] = false

• Il codice di Piè repeat

flag[i] := true; while flag[j] do no_op; <critical section> flag[i] := false; <remainder section>

(6)

Algoritmo 2

• Soddisfa mutua esclusione, ma non il requisito di progresso

• Consideriamo la seguente sequenza di esecuzione

– Pipone flag[i] = true

– Pjpone flag[j] = true

• Pie Pjentrano nel while e non ne escono più !

17

Algoritmo 3

• Usiamo sia turn che flag

– Inizializzazione flag[0] = flag[1] = false, turn = 0 {0 o 1 è lo stesso}.

• Il codice di Piè repeat

flag[i] := true; turn := j; while (flag[j] and turn = j) do

no_op; <critical section> flag[i] := false; <remainder section> until false; •Il codice di Pjè repeat

flag[j] := true; turn := i;

while (flag[i] and turn = i) do no_op; <critical section> flag[j] := false; <remainder section> until false; 18

Algoritmo 3

• Mutua esclusione: se tentano di entrare assieme (ognuno ha già assegnato il suo flag) uno dei due assegnamenti a turn sarà l’ultimo (atomicità delle

istruzioni!) e non cambia fino a che non si rientra

nella entry section

• Progresso: chi esce si dichiara fuori (falso al proprio flag) e quindi il while dell’altro termina e lascia entrare

(7)

Algoritmo 3

• Attesa limitata: il loop non può essere eseguito per più tempo della sezione critica e quindi è limitata dalla durata della sezione critica dell’altro

• Non si ipotizza che i processi viaggino a velocità opportuna

• Per n processi c’è l’algoritmo "del fornaio" (sul libro)

20

Algoritmo del Fornaio

• Soluzione al problema della sezione critica per n processi

• Prima di entrare nella propria sezione critica, un processo riceve un numero.

• Il possessore del numero minore entra nella sezione critica.

• Se due processi Piand Pjricevono lo stesso

numero, se i < j, allora Pientra; altrimenti entra Pj. • I numeri vengono generati sempre in ordine

crescente, ad es., 1,2,3,3,3,3,4,5...

21

Algoritmo del fornaio(Cont.)

• Notazione: ordine lessicografico (# biglietto, process id)

– (a,b) < (c,d) se a < c o se a = c e b < d – max (a0,…, an-1) è un numero k, tale che k ≥ ai

∀ i = 0, … , n – 1

• Variabili condivise inizializzate a false e 0 rispettivamente

var choosing: array [0..n – 1] of boolean; number: array [0..n – 1] of integer

(8)

Algoritmo del fornaio(Cont.)

repeat

choosing[i] := true;

number[i] := max(number[0], number[1], …, number [n – 1])+1; choosing[i] := false;

for j := 0 to n – 1 do begin

while choosing[j] do no-op; while number[j] ≠ 0

and (number[j],j) < (number[i], i) do no-op; end; critical section number[i] := 0; remainder section until false; 23

7.3 Sincronizzazione via Hardware

• Problemi con l’algoritmo del fornaio

– troppo complesso in codice e dati

– i processi fanno busy-waiting =:= attendere usando la CPU

• Altra soluzione: Disabilitare gli interrupt per evitare context switch, ma

– la sezione critica può essere molto lunga – non si può girare a lungo con interrupt

disabilitati

24

7.3 Sincronizzazione via Hardware

– non va bene sui multiprocessori (o richiede troppo tempo, mentre Algoritmo#3 oppure la panetteria vanno bene)

– produce errori nella misurazione del tempo (fatta con le interruzioni)

• ⇒ introdurre istruzioni speciali che scambiano atomicamente il valore di un registro e di una cella di memoria

– TestAndSet – Swap

(9)

7.3 Sincronizzazione via Hardware

shared var lock: boolean;

Inizializzazione lock = false

repeat

while TestAndSet(lock) do no_op; <critical section>

lock := false; <remainder section> until false;

26

7.3 Sincronizzazione via Hardware

• Va bene anche per i multiprocessori con sharing di memoria

• TestAndSet o Swap è una caratteristica del bus e della memoria, difficile da realizzare

27

7.4 Semafori

• Per avere attesa limitata (TestAndSet da sola non lo da) occorre un algoritmo con strutture dati complesse (vedi libro) • Anche con istruzioni speciali si fa

busy-waiting

• Semafori inventati da Dijkstra (1965) per minimizzare busy-waiting

(10)

7.4 Semafori

• Definiti tramite due operazioni

– P (Proberen) e V (Verhogen) oppure – W (Wait) e S (Signal)

• Le operazioni devono essere atomiche. • Semaforo in genere inizializzato a 1

– wait(sem) : while sem ≤ 0 do no_op;

sem := sem-1; – signal(sem) : sem := sem+1;

29

Sezione critica con n processi

• Variabili condivise

– var mutex : semaphore – initially mutex = 1 • Generico processo Pi repeat wait(mutex); critical section signal(mutex); remainder section until false; 30

7.4 Semafori

• Si possono usare anche per problemi di sincronizzazione diversi; ad es., eseguire S1 prima di S2; usiamo un semaforo sync:=0; • P1 esegue: S1; signal(sync); • P2 esegue: wait(sync); S2;

(11)

7.4.2 Implementazione dei Semafori

• Se li implementiamo come le sezioni critiche → busy waiting → sono detti spinlock

⇒ invece, bloccare il processo in attesa in opportuna coda

type semaphore = record value: integer;

L: list of process;

end;

32

7.4.2 Implementazione dei Semafori

• wait(S) :

S.value := S.value -1; if S.value<0 then begin

<add this proc to S.L>

block; end;

• block: invoca scheduler e dispatcher→ commutazione della CPU

33

7.4.2 Implementazione dei Semafori

• signal(S) :

S.value := S.value+1;

if S.value ≤ 0 then begin

<remove proc P from S.L>

wakeup(P); end;

(12)

7.4.2 Implementazione dei Semafori

• Wait e Signal sono sezioni critiche

– molto brevi

– su monoprocessore si può disabilitare le interruzioni – TestAndSet o soluzioni complesse sui

multiprocessori

⇒ busy-wait o interrupt disabilitati ridotti al minimo, ma non eliminati

• Busy-waiting NON DEVE essere adottato per sezioni critiche applicative e quindi di lunga durata

35

7.4.3 Deadlock & Starvation

• I semafori sono molto meno strutturati ⇒ due o più processi possono attendere su un

semaforo sul quale solo uno di loro può fare (ha un programma tale che fa) una signal =:= deadlock

• È CATTIVA PROGRAMMAZIONE!!! (dei processi)

36

7.4.3 Deadlock & Starvation

• P 0 P 1 wait(S); wait(Q); wait(Q); wait(S); … ... signal(S); signal(Q); signal(Q); signal(S);

(13)

7.4.3 Deadlock & Starvation

• Starvation =:= non uscire mai dalla coda di attesa.

• CATTIVA PROGRAMMAZIONE DELLA SIGNAL

– (cioè della exit section dalle sezioni critiche) e quindi dello scheduling

• Applicazioni dei semafori per esercizio

38

Produttore-Consumatore

• Buffer di n posizioni a cui i processi accedono

• Dati condivisi

type item = …

var buffer = array [0..n-1] of item; full, empty, mutex: semaphore; nextp, nextc: item;

full :=0; empty := n; mutex :=1;

39

Produttore-Consumatore

• Produttore

repeat

produce an item in nextpwait(empty); wait(mutex);signal(mutex); signal(full); until false;

(14)

Produttore-Consumatore

• Consumatore repeat wait(full) wait(mutex);

remove an item from buffer to nextc

signal(mutex); signal(empty);

consume the item in nextc

until false;

41

Lettori-Scrittori

• Condividere risorse (ad es. un file) tra molti processi

• Alcuni processi richiedono solo la lettura (processi lettori), altri possono modificare la risorsa (processi scrittori)

• Due o più lettori possono accedere contemporaneamente

• Un processo scrittore deve accedere in mutua esclusione con TUTTI gli altri processi

42

Lettori-Scrittori

• Strutture dati condivise

var mutex, wrt: semaphore (=1); readcount : integer (=0); • Processo scrittore wait(wrt); … writing is performed … signal(wrt);

(15)

Lettori-Scrittori

• Processo lettore wait(mutex);

readcount := readcount +1;

if readcount = 1 then wait(wrt);

signal(mutex); … reading is performed … wait(mutex); readcount := readcount – 1;

if readcount = 0 then signal(wrt);

signal(mutex):

44

Problema dei filosofi

• 5 filosofi passano la vita pensando e mangiando • I filosofi condividono un

tavolo rotondo con 5 posti.

• Un filosofo per mangiare deve usare due bacchette Dati condivisi

var chopstick: array [0..4] of semaphore; (=1 initially)

45

Problema dei filosofi

• Philosopher i: repeat wait(chopstick[i+1 mod 5]) wait(chopstick[i]) … eat … signal(chopstick[i]); signal(chopstick[i+1 mod 5]); … think … until false;

(16)

Problema dei filosofi

• La soluzione presentata non esclude il deadlock (diverse soluzioni)

– solo 4 filosofi a tavola contemporaneamente – prendere le due bacchette insieme (sezione

critica!)

– prelievo asimmetrico in un filosofo

• Inoltre, si deve escludere starvation di un filosofo

47

7.6 Regioni Critiche Condizionali

• 7.7 Monitor

• Costrutti linguistici inventati per evitare i problemi di programmazione che facilmente si fanno con i semafori

• Attenzione con i thread: in tale ambiente ci sono in genere solo semafori o poco più • Li vedrete nel prossimo corso

48

7.9 Transazioni Atomiche

• Mutua esclusione ⇒ esecuzione indivisibile di sezioni critiche

• Transazione Atomica =:= transazione indivisibile che avviene completamente oppure non avviene affatto

⇒ non sono osservabili stati intermedi ⇒ se non può essere completata con successo,

lascia il mondo inalterato

• È un concetto del mondo dei data base, ma

– utile nei SO

(17)

7.9.1 Modello del Sistema

Transazionale

• Transazione =:= funzione logica costituita da molteplici operazioni, fra le quali interessano

– read/write – commit/abort

– abort causato dal SO o dall’hardware (crash!)

• Quando la transazione abortisce bisogna ripristinare lo stato come prima dell’inizio della transazione ⇒ roll-back.

– È compito del sistema transazionale

(SO+ambiente specifico) realizzare il roll-back.

50

Proprietà delle Memorie

• Volatile: si perde per crash (ad es RAM) • Non volatile: sopravvive ai crash

(EEPROM, dischi, nastri, etc) ma ha comunque una significativa probabilità di perdita

• Stabile: non si perde mai (quasi, mai apprezzabilmente)

51

Problematiche

⇒ replicare l'informazione, scriverla e modificarla in modo appropriato ⇒ realizzare transazioni atomiche in un

ambiente in cui ci può essere perdita di informazione su memoria volatile (cioè la memoria volatile non è considerata sufficiente)

(18)

7.9.2 Ripristino Basato su Log

• Log =:= registrazione (non logaritmo!) ⇒ registrare su memoria stabile tutte le

modifiche fatte dalla transazione ai dati (che in genere vengono tenuti su memoria non volatile)

• Write-ahead logging: ogni record contiene

– nome della transazione – nome del dato – vecchio valore – nuovo valore

53

7.9.2 Ripristino Basato su Log

• Record per registrare inizio, commit, abort di transazione

• Il log record di write viene scritto prima di fare la write

– due write al posto di una!

∃ due procedure undo(Ti) e redo(Ti) che si basano sul log.

• Sono ambedue idempotenti

54

7.9.2 Ripristino Basato su Log

• Se il log contiene < Tistart> e < Ticommit> e la memoria non volatile si è perduta ⇒ redo(Ti)

• Se il sistema ha un crash allora

– se esiste < Tistart> ma non esiste < Ticommit>

⇒ undo(Ti)

– se esistono ambedue < Tistart> e < Ticommit>

(19)

7.9.3 Checkpoint

• Possibile che ad ogni crash dobbiamo rifare tutte le transazioni eseguite dal crash precedente? No!

• Checkpoint =:= punto di verifica

– tutti i record di log da memoria volatile a memoria stabile

– tutti i dati da memoria volatile a memoria stabile

– emettere un log record <checkpoint> sul log in memoria stabile

56

7.9.3 Checkpoint

• Ci si comporta poi così:

– 1. Ad ogni crash si esamina il log cercando dal fondo (all’indietro) l’ultimo record di <checkpoint> che vi si trova

– 2. A partire da tale record di checkpoint si trova all’indietro (prima di tale ultimo <checkpoint>) la più recente Tiche è partita prima del

checkpoint→ la prima < Tistart> che si trova

leggendo all’indietro il file di log

57

7.9.3 Checkpoint

• Nota bene: l’ipotesi è che nel sistema si esegua una transazione alla volta

– 3. Si legge in avanti il file di log facendo undo o redo di tutte le transazioni che si trovano, come nel caso che non si utilizzi il checkpoint

(20)

7.9.4 Transazioni Atomiche

Concorrenti

• Devono godere della proprietà di serializzabilità =:= l’effetto finale complessivo ottenuto deve essere identico all’effetto che si sarebbe ottenuto con uno degli ordini di esecuzioni seriale possibili ⇒ abbiamo un problema di mutua esclusione

e sezioni critiche

• Utilizzare un unico semaforo è troppo restrittivo → poco parallelismo

59

7.9.4 Transazioni Atomiche

Concorrenti

• Il parallelismo massimo dipende dal modo con cui i dati vengono usati nelle transazioni • Serial schedule di due transazioni =:=

ognuna delle due transazioni è eseguita in modo atomico, senza parallelismo

T0 T1 read(A) write(A) read(B) write(B) -- read(A) - write(A) - read(B) - write(B) 60

7.9.4 Transazioni Atomiche

Concorrenti

• Se dobbiamo eseguire n transazioni abbiamo n! possibili serial schedule • Ci interessano le schedule non seriali, che

però siano equivalenti ad una schedule seriale

• Schedule non seriali =:= schedule in cui le transazioni si sovrappongono in tempo almeno in parte.

(21)

7.9.4 Transazioni Atomiche

Concorrenti

• Quando sono possibili e ancora corrette? • Operazioni Oi∈ Ti, Oj∈ Tjsono dette

conflittuali sse accedono allo stesso dato e almeno una delle due è una write

• Operazioni non conflittuali possono essere eseguite in ordine scambiato senza

modificare l’effetto finale =:= scambio non conflittuale.

62

7.9.4 Transazioni Atomiche

Concorrenti

• Se con una serie di scambi non conflittuali si ottiene una schedule seriale allora

– la schedule di partenza è conflict serializable – è equivalente ad una seriale

– è una valida esecuzione – ha maggior parallelismo!

63

7.9.4 Transazioni Atomiche

Concorrenti

• Esempio di conflitto in una schedule non seriale T0 T1 read(A) -write(A) - ↑ conflitto - read(A) ↓ - write(A) ↑non read(B) - ↓ conflitto write(B) -- read(B)

(22)

Protocollo di Locking

• Per assicurare serializzabilità associamo un lock ad ogni dato e seguiamo una regola di comportamento (=:=protocollo) per acquisire i lock prima di accedere alle variabili

• Come nel caso di lettori/scrittori:

– lock condiviso per leggere – lock esclusivo per scrivere

• Stranamente, rilasciare il lock subito dopo l’ultimo accesso ad un dato può produrre non serializzabilità

65

Protocollo di locking a due fasi

• fase di crescita: si acquisiscono lock ma non se ne rilascia nessuno

• fase di assottigliamento: si rilasciano lock ma non se ne acquisisce nessuno

– la transazione è costituita da una sola crescita seguita da un solo assottigliamento

– appena si fa un rilascio non si può che continuare a rilasciare

66

Protocollo di locking a due fasi

• Può dare deadlock

• Non produce tutte le possibili schedule serializzabili (quindi alcune schedule con parallelismo maggiore non vengono generate/permesse)

(23)

Protocolli basati su Timestamp

• Determinare in anticipo (prima che a run-time) l’ordinamento fra le operazioni delle transazioni

⇒ associare a Tiun timestamp TS(Ti) • Timestamp totalmente ordinati, assegnati

dal sistema alla partenza di ogni Ti • TS: clock di sistema o contatore logico

68

Protocolli basati su Timestamp

• Se TS(Ti) < TS(Tj) allora la schedule non

seriale deve essere equivalente alla seriale (Ti; Tj)

• Ad ogni dato Q associare

– W_TS(Q) =:= il max TS di ogni Tiche ha

eseguito con successo write(Q), e

– R_TS(Q) =:= il max TS di ogni Tiche ha

eseguito con successo read(Q).

– NB: valori da tenere aggiornati a run-time

69

Protocolli basati su Timestamp

• Logica: far fallire quelle operazioni che farebbero acquisire alla transazione dati sbagliati (attraverso la read) o

invaliderebbero dati già acquisiti per la write.

• Se quando tento di leggere ho che TS < W_TS(Q), i dati che leggo sono sbagliati

(24)

Protocolli basati su Timestamp

• Se quando tento di scrivere ho che TS < R_TS(Q), i dati che sovrascrivo sono stati letti e presi per buoni da un’altra transazione • Quando posso eseguire l’operazione il R_TS

o W_TS di Q viene aggiornato

• Quando l’operazione fallisce, la transazione è abortita, rolled-back e deve essere ritentata, ma questa volta avrà un TS maggiore!

71 T0(TS=10) T1(TS=15) read(A) R_TS(A) = 10 -write(A) W_TS(A) = 10 -- read(A) R_TS(A) = 15 - write(A) W_TS(A) = 15 •T0 prima di T1: OK ! •T1 prima di T0 : abort di T0

Riferimenti

Documenti correlati

della erogazione delle cure che rende la Medicina Generale indeterminata assieme alle cure primarie che devono invece essere definite e di alta qualità..

Gli avverbi «detto» e «alias» nonché le particelle devono essere indicati integralmente e nell'ordine dello stato civile. (6) Indicare tutti i nomi nell'ordine dello

Camillo Venesio sostiene i meriti dell'approccio di Basileo e spiega che la Banca del Piemonte, che presiede ha sviluppato un modello di rating interno con quattro pilastri

- Soluzione per infusione: L'intero contenuto della fiala da 25 ml di Sporanox EV concentrato deve essere diluito nella sacca da infusione contenente la soluzione

La  verifica  della  regolare  costituzione  del  Consiglio  Nazionale  è  a  cura  del  Segretario  Nazionale  o  di  un  suo  sostituto  scelto  dal 

Pertanto la delegazione di parte sindacale ammessa alle trattative nella contrattazione integrativa a livello di istituzione scolastica deve essere integrata

[9 punti] Dato il seguente frammento di schema relazionale, in cui i vincoli di integrità referenziale sono quelli ovvi,.. Esperto(codiceEsperto, cognome, nome)

Quando un'auto (già nella rotatoria oppure in arrivo da una strada confluente) vuole entrare in un arco di rotatoria, deve (i) attendere che ci sia capacità residua e (ii) seguire