# Semafori concorrenti - HHO *(scrivi il resto)* ## No deadlock Per assurdo, supponiamo deadlock al tempo T: i semafori si bloccano su P (idrogeno su P(sH), ossigeno su uno dei P(sO)) * Invarianti semaforici ``` val(sH,T) = 0 => NP(sO, T) = NV(s0,T) // nessuna P eseguita con successo, il valore di sH e' 0 perche' il processo e' bloccato val(sO,T) = 0 => NP(sH, T) = NV(sH, T) + 2 ``` * utilizziamo gli invarianti topologici ``` NP (sH, T) = NV (sO, T) => 0 <= NP(sO,T) - NV (sH,T) <= 1 ``` * Cerchiamo un assurdo: ``` NV (sH, T) + 2 = NP (sH, T) = NV (sO, T) <= NV (sH,T) + 1 ``` * Il risultato non e' ammissibile (2<1) quindi non e' possibile ottenere deadlock dalla soluzione presentata. # Uso di Semafori Privati Semafori (binari/contatori) di **proprieta' di uno o piu' processi**. I proprietari sono gli unici a poter eseguire P sul suddetto semaforo, ma **non ci sono restrizioni sull'uso di V**. ## Utilizzi * Svegliare un determinato processo, per rendere la gestione di una coda di processi indipendente dall'implementazione del semaforo (kernel space). In questo modo posso stabilire una gerarchia di prorita' tra processi. ### Vantaggi Precisione nella gestione di pool di processi. ### Svantaggi Complessita' aggiunta all'implementazione: il programma deve mantenere piu' semafori (anche a livello di kernel: i semafori privati possono essere implementati **al di fuori** di una coda). Il caso piu' comune e' la necessita' di dover mantenere un semaforo per ogni processo in coda, invece che uno solo (e una coda). ## Schemi di gestione ### Schema 1 Contesto: ``` < struttura dati gestore > semaphore mutex; semaphore priv[n] = {0}; // array di semafori privati, ognuno dedicato a un processo (n processi) ``` #### Procedura di acquisizione ``` public int acquisisci (int p) { // simile ai semafori condizionali, anche qui dovremo testare l'ottenibilita' di una risorsa int j; // risorsa da allocare P (mutex); if ( ! Cp) { // Cp e' la condizione per il processo p < registrare sospensione del processo p > V (mutex); P (priv[p]); < registro che il processo p non e' piu' sospeso > } < alloco risorsa j > V (mutex); return j; } ``` #### Procedura di rilascio ``` public void rilascio (int j) { int p; P (mutex); < registro rilascio risorsa j > if ( < esiste almeno 1 processo sospeso > ) { < scelgo il processo p con massima priorita' > V (priv[p]); } else { V (mutex); } } ``` ### Esempio: allocazione di pool di risorse ``` int avail = M; bool libero[M] = {true}; bool sospeso[n] = {false}; int cnts = 0; // conta i sospesi semaphore mutex; semaphore priv[n] = {0}; // array di semafori privati, ognuno dedicato a un processo (n processi) ``` #### Procedura di acquisizione ``` public int acquisisci (int p) { int j; // risorsa da allocare P (mutex); if ( avail == 0) { sospeso[p] = true; cnts++; V (mutex); P (priv[p]); sospeso[p] = false; cnts--; } avail--; while (libero[j]) j++; libero[i] = false; V (mutex); return j; } ``` #### Procedura di rilascio ``` public void rilascio (int j) { int p; P (mutex); avail++; libero[j] = true; if ( cnts > 0 ) { p = maxpriority (sospeso); // ottengo il processo con priorita' massima V (priv[p]); } else { V (mutex); } } ``` ### Schema 2 Siccome e' complesso dal punto di vista della programmazione strutturata (alto livello), e' stato proposto un secondo schema. #### Procedura di acquisizione ``` public void acquisizione (int p) { P (mutex); if (Cp) { < alloca risorsa > V (priv[p]); } else { < registro sospensione processo p> } V (mutex); P (priv[p]); // a seconda del valore di Cp, questa P e' sospensiva (!Cp) o vanificata dalla V (Cp) } ``` *nota: la risorsa in questo caso e' garantita dal processo che rilascia, perche' se Cp non e' verificata, questa procedura non alloca nulla* #### Procedura di rilascio ``` public void rilascio (int j) { int p; P (mutex); < registro rilascio risorsa j > if ( < esiste almeno 1 processo sospeso > ) { < scelgo il processo p con massima priorita' > < alloco risorsa > < registro che p non e' sospeso > V (priv[p]); } V (mutex); } ``` #### Pro e contro della soluzione 2 * Un problema della soluzione 2 e' che e' limitato a 1 dato libero per volta (se i processi consumatori hanno bisogno di piu' di un "dato" (risorsa), non ho modo di sapere quanti ne sto rilasciando), mentre la soluzione 1 implementata con un *while* risolve questo problema. * Siccome il processo rilasciante si occupa anche di allocare le risorse, non si puo' effettuare il passaggio di parametri. Questo caso e' ovviato dall'utilizzo di un array condiviso *allocazione* che fornisce l'indice della risorsa che e' stata allocata. # Problema dell'accesso unico (senso unico alternato) - Readers & Writers ## Precedenza agli scrittori L'implementazione con precedenza agli scrittori permette di bloccare i readers se un writer e' nella SC, ma anche se un writer e' in attesa. ## Soluzione bilanciata Utilizzando dei semafori privati, e' possibile associare un semaforo privato a lettori e scrittori, ottenendo un accesso limitato degli scrittori alla SC, e quindi ottenendo una soluzione equilibrata.