Problema dei 5 filosofi

ciascuno di questi filosofi può mangiare o può pensare, per mangiare richiede due forchette (risorse), le forchette sono una per ogni filosofo e sono tutte condivise. Per risolvere questo problema abbiamo diverse soluzioni (è importante usare 5 filosofi, perché 5 è il numero minimo di filosofi che ci permettono di trovare una soluzione generalizzabile)

Soluzioni generali
  1. Soluzione 1:
int N=5 
function philosopher(int i) 
	think() 
	take_fork(i) 
	take_fork((i+1) mod N) 
	eat() 
	put_fork(i) 
	put_fork((i+1) mod N)

Questa procedura non è esente da difetti. Se tutti i filosofi diventano affamati nello stesso identico momento e afferrano contemporaneamente la prima forchetta, il sistema si blocca in una situazione di stallo definitivo (deadlock), in quanto nessuno di loro avrà mai a disposizione la seconda forchetta necessaria per mangiare.

  • Soluzione 2: Per evitare lo stallo, si può prevedere che un filosofo, qualora non riesca a prendere la seconda forchetta, rilasci la prima e attenda un tempo prestabilito prima di riprovare. Sebbene eviti il deadlock, questa strategia introduce un nuovo problema: se i filosofi agiscono in perfetta sincronia, aspetteranno tutti lo stesso numero di secondi per poi riattivarsi e bloccarsi di nuovo simultaneamente. Il sistema entra in un ciclo infinito di azioni a vuoto.
  • Soluzione 3: Questa soluzione è identica alla precedente, ma introduce un tempo di attesa casuale (random) anziché fisso. È un approccio semplice ma molto efficace: l’attesa casuale rompe la simmetria e la sincronia tra i filosofi, risolvendo di fatto il problema che creava la soluzione precedente.
  • Soluzione 4: La soluzione più elegante e robusta prevede l’utilizzo di primitive di sincronizzazione, come un semaforo o un monitor.
Codice usando un semaforo mutex

Di seguito lo pseudocodice di una soluzione usando un semaforo mutex

  1. Inizializzazione e Variabili Globali (In alto a sinistra): In questa sezione vengono definiti i parametri del sistema:
    • Stati: Ogni filosofo può essere in uno di tre stati: THINKING (pensa), HUNGRY (vorrebbe mangiare) o EATING (possiede le forchette e mangia).
    • Semaforo mutex: Garantisce l’esclusione mutua quando si accede o si modifica l’array degli stati.
    • Semafori s[N]: Un semaforo per ogni filosofo. Vengono usati per “bloccare” un filosofo se le forchette non sono disponibili.
  2. Funzione philosopher (Al centro a sinistra): Rappresenta il ciclo di vita infinito di ogni singolo processo (filosofo). È strutturato in una sequenza logica: il filosofo pensa, cerca di prendere le forchette (take_forks), mangia e infine le posa (put_forks) per ricominciare il ciclo.
  3. Funzioni di Gestione Forchette (A destra)
    • take_forks(i): Il filosofo dichiara di avere fame e prova a mangiare chiamando test(i). Se i vicini non stanno mangiando, passa a EATING. Altrimenti, si blocca sull’istruzione down(s[i]) finché un vicino non lo “sveglia”.
    • put_forks(i): Dopo aver mangiato, il filosofo torna a pensare. Fondamentalmente, chiama test sui suoi vicini (left e right) per vedere se la sua azione di posare le forchette permette a uno di loro (che era in attesa) di iniziare a mangiare.
  4. Logica di Controllo e Vicinato (In basso) - left / right: Funzioni matematiche (usando l’operatore modulo N) per identificare correttamente i vicini in un tavolo circolare. - test(i): È il cuore della logica. Verifica se il filosofo i è affamato e se entrambi i suoi vicini non stanno mangiando. Se queste condizioni sono vere, lo stato diventa EATING e viene inviato un segnale (up) al semaforo del filosofo per sbloccarlo.
Codice usando un monitor

Questa è un implementazione è molto simile a quella fatta con il semaforo l’unica differenza è che qui sarà il sistema operativo a garantire l’accesso singolo al monitor e quindi alla gestione degli stati

Problema dei lettori-scrittori

Ha presentato altre soluzione

semafori

Soluzione basata sui semafori

monitor

Soluzione basata sui monitor

Scheduling

la scelta di quali processi va avviato viene fatta dallo scheduler, in pratica sceglie dalla coda dei processi pronti. Per fare ciò i processi si dividono in due tipi:

  • processi CPU bounded: un processo che usa principalmente la CPU
  • processi I/O bounded: un processo che fa principalmente operazioni di input/output quello che fa lo scheduling è inserire tutti i processi IO bound dentro la CPU in modo da avviare tutte le operazioni di IO che sono lente, mentre aspetta una risposta avvia i processi CPU bounded, una volta che questi sono finiti riprende l’esecuzione dei processi IO bounded che a quel punto avranno quasi sicuramente finito

Dispatcher: è l’esecutivo dello scheduler, dopo aver fatto le scelte il dispatcher le mette in atto, il tempo che ci mette il dispatcher per fare le cose si chiama: latenza di dispatcher