Parte I — Concorrenza a memoria condivisa · Capitolo 7

Monitor e variabili condizione

~40 min di lettura5 widget interattivi

In questo capitolo

  1. Oltre i semafori: i monitor
  2. Variabili condizione: operazioni e semantica
  3. Discipline di segnalazione
  4. Producer-Consumer con monitor
  5. Readers-and-Writers con monitor
  6. Monitor in Java: synchronized, wait, notify
  7. Bounded buffer come monitor in Java
  8. ReentrantLock e Condition: monitor di alto livello
  9. Semantica di segnalazione in Java: effetti indesiderati
  10. Monitor e semafori: equivalenza espressiva
  11. Confronto: semafori vs variabili di condizione
  12. Verifica le tue conoscenze

1. Oltre i semafori: i monitor

I semafori sono potenti ma di basso livello: il professore sottolinea che sono espressivi e permettono di risolvere qualsiasi problema di interazione, ma il loro uso non e banale perche sono un meccanismo comunque di basso livello, facile da sbagliare — programmi soggetti a errori, difficili da usare in programmi concorrenti complessi. Da qui nasce l'esigenza di costrutti di piu alto livello: i Monitor, introdotti da Brinch Hansen (1973) e generalizzati da Tony Hoare (1974).

flowchart LR subgraph "Semafori (basso livello)" A["wait/signal
mutex + sync misti"] end subgraph "Monitor (alto livello)" B["mutua esclusione
implicita +
variabili di condizione"] end A -->|"error-prone"| B

I monitor sono l'evoluzione naturale dei semafori: un modulo che incapsula l'accesso concorrente a uno stato condiviso, fornendo mutua esclusione intrinseca (le procedure vengono eseguite in mutua esclusione, al massimo un processo attivo per volta) e sincronizzazione esplicita tramite variabili di condizione che permettono di sospendere e risvegliare i processi.

Un Monitor e un'astrazione di dati per la programmazione concorrente che incapsula in un'unica entita:

monitor MonitorName {
    // variabili permanenti (stato privato)
    // codice di inizializzazione
    procedure (o entry) NomeOp(params) {
        // corpo
    }
}

Proprieta fondamentali

Il professore nota che il monitor e una generalizzazione del concetto di kernel dei sistemi operativi: invece di avere un unico kernel centralizzato che gestisce tutte le sezioni critiche, ogni monitor e un "mini-kernel" decentralizzato specializzato per un dato condiviso. E anche una generalizzazione del concetto di oggetto nella OOP: come un modulo OOP che incapsula dati + operazioni + politica di sincronizzazione/mutua esclusione, includendo il costrutto di base per garantire la correttezza dell'accesso concorrente.

Idea chiave

Un monitor e un data type astratto che incapsula stato, operazioni e politica di sincronizzazione. E come un modulo (o un oggetto) che fornisce intrinsecamente mutua esclusione su tutti i suoi metodi.

L'esempio piu semplice e un contatore thread-safe:

monitor Counter {
    int count;

    procedure inc() {
        count := count + 1
    }

    procedure getValue(): int {
        return count
    }
}

L'esempio del contatore mostra la potenza dell'astrazione: non c'e alcun wait/signal esplicito per la mutua esclusione. Se due processi chiamano inc() contemporaneamente, il monitor garantisce l'atomicita: due thread non eseguiranno mai inc() nello stesso momento. Il docente sottolinea che la mutua esclusione e implicita, fornita dall'implementazione del monitor: le operazioni sono eseguite atomicamente le une rispetto alle altre, senza che il programmatore usi semafori o altri meccanismi.

2. Variabili condizione: operazioni e semantica

La sola mutua esclusione non basta: serve anche la sincronizzazione. Le variabili condizione (condition variables) sono un tipo di dato primitivo che i monitor mettono a disposizione per la sincronizzazione esplicita: permettono di sospendere (waitC) e risvegliare (signalC) processi all'interno di un monitor, rappresentando condizioni (eventi) sullo stato del monitor in attesa di essere soddisfatte. Ogni variabile condizione e associata a una coda FIFO di processi bloccati su quella condizione.

Operazioni

OperazioneComportamento
waitC(cond)Sospende il processo chiamante e rilascia il lock del monitor (fondamentale: senza questo rilascio, nessun altro processo potrebbe entrare per cambiare lo stato). Il processo viene aggiunto alla coda della condizione.
signalC(cond)Se la coda non e vuota, sblocca il primo processo in attesa (lo porta a ready); se la coda e vuota, non ha effetto
emptyC(cond)Verifica se la coda della condizione e vuota
signalAllC(cond)Sblocca tutti i processi in attesa sulla condizione (utile quando non si sa quale risvegliera)
waitC(cond, rank)Attesa con priorita: il processo viene inserito in coda in ordine crescente di rank
minrank(cond)Restituisce il rank del processo in testa alla coda

Formalmente, le due operazioni fondamentali si definiscono cosi:

waitC(cond) = < append p to cond.queue
                p.state := blocked
                monitor.lock := release >

signalC(cond) = < if cond.queue != empty
                    q := remove head of cond.queue
                    q.state := ready >
Idea chiave

waitC rilascia il lock del monitor (altrimenti nessun altro processo potrebbe entrare per cambiare lo stato e risvegliarlo). signalC risveglia il processo in testa alla coda (semaforo forte). A differenza di signal su semaforo — che incrementa sempre il contatore — signalC non ha effetto se la coda e vuota.

Esempio: SynchCell (cella sincronizzata)

Una cella che permette a un processo di depositare un valore e a un altro di prelevarlo, con sincronizzazione automatica:

monitor SynchCell {
    int value;
    boolean available := false;
    cond isAvail;

    procedure set(int v) {
        value := v
        available := true
        signalC(isAvail)
    }

    procedure get(): int {
        if (!available)
            waitC(isAvail)
        return value
    }
}

Un processo chiama get() ma se il valore non e ancora disponibile (available = false), si sospende su isAvail. Quando un altro processo chiama set(), imposta il valore e segnala isAvail, risvegliando il processo in attesa. Il professore sottolinea che il waitC rilascia automaticamente il lock del monitor, permettendo cosi ad altri processi di chiamare set() per fornire il dato.

Il professore fa anche notare: se piu processi sono in waitC su isAvail, un singolo signalC ne sveglia solo uno. Per svegliarli tutti, si usa signalAllC — utile quando non si sa quanti processi stanno aspettando.

3. Discipline di segnalazione

Quando un processo esegue una signalC, sia il signaller che il signalled potrebbero essere pronti a proseguire nel monitor, ma solo uno puo avere accesso esclusivo: si pone il problema di chi debba continuare, il processo che segnala o quello che viene risvegliato? Le tre discipline classiche differiscono per l'ordine di priorita tra signaller (S), waiting (W — processi in attesa sulla condizione) ed external processes (E — processi bloccati sull'entry del monitor):

Signal and Continue (nonpreemptive): il processo che segnala continua l'esecuzione; il processo risvegliato eseguira in un momento successivo, quando potra riacquisire il lock del monitor, competendo con gli altri processi. Precedenze: E < W < S (processi bloccati sull'entry < processi in attesa sulla condizione < processo che segnala). E la disciplina piu comune (usata da Java, per esempio — che adotta pero la variante E = W < S).

Signal and Wait (preemptive): il processo risvegliato viene eseguito immediatamente; il processo che ha segnalato si mette in attesa, competendo ad armi pari con i processi esterni (E) per il lock. Precedenze: E = S < W — i risvegliati hanno priorita massima.

Signal and Urgent Wait (Immediate Resumption Requirement): come Signal and Wait, ma il processo che ha segnalato ha priorita rispetto ai processi in attesa sul lock del monitor quando puo riprendere. Precedenze: E < S < W. E la disciplina classica per i monitor nella letteratura originale, quella definita da Hoare.

In forma tabellare compatta:

DisciplinaOrdine di precedenzaComportamento
Signal & ContinueE < W < SIl segnalante continua; il risvegliato compete con gli altri
Signal & WaitE = S < WIl risvegliato esegue subito; il segnalante aspetta
Signal & Urgent WaitE < S < WIl risvegliato esegue subito; il segnalante ha priorita sugli altri in attesa del lock

Legenda: S = segnalante, W = processi in waitC, E = processi bloccati sull'entry del monitor.

Conseguenza pratica: if vs while

A seconda della disciplina di segnalazione, il test sulla condizione dopo un waitC puo essere un if o un while: l'uso del while e sempre sicuro ed e necessario con Signal and Continue, perche la condizione potrebbe essere cambiata da un altro processo tra il risveglio e la riacquisizione del lock. Il professore nota che la scelta della disciplina ha implicazioni pratiche: con Signal & Continue, dopo un waitC(cond) e consigliabile usare while (!condizione) invece di if (!condizione) per gestire possibili risvegli spuri (il signalled potrebbe trovare la condizione ancora falsa perche un altro processo l'ha cambiata nel frattempo).

// Con Signal and Wait (preemptive): if basta (ma while e sempre sicuro)
if (!B) waitC(cond)

// Con Signal and Continue: while necessario!
while (!B) waitC(cond)
Nota del redattore

La differenza tra S&C e S&W ha implicazioni pratiche. Con S&C (come in Java), dopo signal il thread risvegliato deve competere per il lock con chiunque altro sia in attesa. Con S&W, il risveglio e immediato. Questo influenza come si scrivono i cicli di controllo delle condizioni.

4. Producer-Consumer con monitor

Ecco l'implementazione del bounded buffer come monitor, che incapsula automaticamente la mutua esclusione sull'accesso al buffer e la sincronizzazione tramite variabili condizione. Il buffer e implementato come array circolare:

monitor BoundedBuffer {
    int[] elems := new int[MAX_ELEMS]
    int first := 0, last := 0
    cond notFull, notEmpty

    procedure put(int elem) {
        if ((last + 1) % MAX_ELEMS = first)
            waitC(notFull)
        elems[last] := elem
        last := (last + 1) % MAX_ELEMS
        signalC(notEmpty)
    }

    procedure take(): int {
        if (first = last)
            waitC(notEmpty)
        int elem := elems[first]
        first := (first + 1) % MAX_ELEMS
        signalC(notFull)
        return elem
    }
}

// Producer                    // Consumer
loop                          loop
    ElemType el := produce        ElemType el := BoundedBuffer.take()
    BoundedBuffer.put(el)         consume(el)

Il codice produttore e consumatore diventa estremamente semplice: chiamano rispettivamente put e take sul monitor, senza preoccuparsi di lock espliciti o semafori. La variabile condizione notFull blocca il produttore se il buffer e pieno; notEmpty blocca il consumatore se il buffer e vuoto. Quando un produttore aggiunge un elemento, segnala notEmpty per sbloccare un eventuale consumatore in attesa; quando un consumatore preleva, segnala notFull per sbloccare un eventuale produttore.

Il professore commenta: "Questa soluzione e molto piu semplice e chiara rispetto a quella con semafori. Il monitor gestisce automaticamente la mutua esclusione sul buffer, e le variabili condizione notFull/notEmpty gestiscono la sincronizzazione."

Nota del redattore

Nell'implementazione con array circolare (dimensione fissa MAX_ELEMS), lo spazio utile e MAX_ELEMS-1 per distinguere buffer pieno da buffer vuoto. Con disciplina Signal & Continue, il controllo if dovrebbe essere while per gestire risvegli spuri, e si puo usare una variabile di stato esplicita (come un contatore) invece di controllare direttamente se il buffer e pieno/vuoto.

5. Readers-and-Writers con monitor

L'implementazione del RWLock come monitor mostra la potenza delle variabili condizione per gestire politiche di sincronizzazione complesse: la politica di sincronizzazione viene incapsulata nella struttura dati stessa, usando due variabili di condizione (okToRead, okToWrite). Ecco la versione con disciplina Signal & Continue:

// Invariant: (nr == 0 or nw == 0) and (nw <= 1)
monitor RWLock {
    int nr, nw := 0
    cond okToRead, okToWrite

    procedure request_read() {
        while (nw > 0)
            waitC(okToRead)
        nr := nr + 1
    }

    procedure release_read() {
        nr := nr - 1
        if nr = 0
            signalC(okToWrite)
    }

    procedure request_write() {
        while (nr > 0 or nw > 0)
            waitC(okToWrite)
        nw := nw + 1
    }

    procedure release_write() {
        nw := nw - 1
        signalC(okToWrite)
        signalAllC(okToRead)
    }
}

L'invariante fondamentale: (nr == 0 or nw == 0) and (nw <= 1) — o ci sono solo reader, o solo un writer (o nessuno). Questa soluzione garantisce che i lettori non si escludano tra loro ma escludano gli scrittori, e viceversa. release_write() fa due segnalazioni: signalC(okToWrite) per sbloccare un writer in attesa (se c'e), e signalAllC(okToRead) per risvegliare tutti i reader in attesa.

Il docente sottolinea l'uso di while invece di if nella condizione di wait — pattern fondamentale per gestire la riapertura della condizione dopo signal (soprattutto con semantica Signal & Continue).

Attenzione

Con Signal & Continue, dopo esser stati risvegliati, i processi devono ricontrollare la condizione. Un while loop e obbligatorio, non opzionale. Questo e un errore comune che porta a bug sottili.

Il professore mostra anche una seconda versione, piu compatta, che usa il pattern "cascading signal": dopo startRead(), il primo reader sbloccato chiama a sua volta signalC(okToRead) per risvegliare il successivo, creando una catena. Questa versione elimina il while e torna a usare if per il controllo delle condizioni.

procedure startRead() {
  if writers != 0
    waitC(okToRead);
  readers := readers + 1;
  signalC(okToRead);    { risveglia il prossimo reader in cascata }
}

procedure endWrite() {
  writers := writers - 1;
  if emptyC(okToRead)
    then signalC(okToWrite)   { se nessun reader aspetta, sblocca un writer }
    else signalC(okToRead);   { altrimenti sblocca il primo reader }
}

6. Monitor in Java: synchronized, wait, notify

Java offre due approcci per implementare monitor. Il primo, di piu basso livello, sfrutta i meccanismi nativi della JVM: synchronized, wait, notify, notifyAll.

Quando un metodo e dichiarato synchronized, la JVM genera automaticamente le istruzioni bytecode monitorenter e monitorexit. Ogni oggetto Java ha un intrinsic lock (o monitor lock) associato.

Trappola comune

Un errore frequente: chiamare wait() o notify() fuori da un blocco synchronized causa IllegalMonitorStateException. Il thread chiamante deve essere il proprietario del lock sull'oggetto. Il professore ha mostrato questo errore in aula con un esempio dal vivo.

Pattern del monitor in Java

Regole per implementare il monitor pattern:

  1. Tutti i metodi pubblici devono essere synchronized
  2. Nessun campo pubblico
  3. Il codice del monitor deve accedere solo a oggetti completamente confinati al suo interno
  4. E disponibile un'unica variabile di condizione implicita (l'oggetto stesso)

Esempio: SynchCell

Semantica di wait

Semantica di notify e notifyAll

Spurious wakeup e il pattern while

La documentazione Java avverte: "A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup." Per questo il pattern corretto e sempre wrappare wait in un ciclo while che riverifica la condizione:

synchronized void get() {
    while (!available) {    // SEMPRE while, mai if!
        try { wait(); } catch (InterruptedException ex) {}
    }
    return value;
}

Un'altra limitazione del meccanismo di base: piu predicati di condizione diversi devono essere associati alla stessa unica variabile di condizione (l'oggetto). Questo forza l'uso di notifyAll invece di notify, e il ciclo while permette a ogni thread di riverificare la propria condizione specifica al risveglio.

7. Bounded buffer come monitor in Java

Implementiamo il bounded buffer produttori-consumatori usando il monitor pattern in Java, con synchronized/wait/notifyAll.

public class BoundedBuffer<Item> {
    private int first, last, count;
    private Item[] buffer;

    public BoundedBuffer(int size) {
        first = last = count = 0;
        buffer = (Item[]) new Object[size];
    }

    public synchronized void put(Item item) throws InterruptedException {
        while (isFull()) { wait(); }
        last = (last + 1) % buffer.length;
        count++;
        buffer[last] = item;
        notifyAll();
    }

    public synchronized Item get() throws InterruptedException {
        while (isEmpty()) { wait(); }
        first = (first + 1) % buffer.length;
        count--;
        notifyAll();
        return buffer[first];
    }

    public synchronized boolean isEmpty() { return count == 0; }
    public synchronized boolean isFull() { return count == buffer.length; }
}
Nota: notifyAll e overhead

Domanda del professore: e davvero necessario usare notifyAll invece di notify? Se ci sono sia produttori che consumatori in attesa sulla stessa variabile di condizione, notify potrebbe svegliare un thread "sbagliato" (es. un produttore invece di un consumatore), che si rimetterebbe in attesa. notifyAll sveglia tutti, introducendo un overhead ma garantendo il progresso. Con ReentrantLock e Condition separate, questo problema si risolve elegantemente.

8. ReentrantLock e Condition: monitor di alto livello

La libreria java.util.concurrent fornisce classi per implementare monitor con variabili di condizione multiple, superando le limitazioni del meccanismo synchronized.

La classe ReentrantLock e un'implementazione rientrante di lock mutex. Da un lock si possono creare multiple Condition, che rappresentano variabili di condizione distinte.

SynchCell con ReentrantLock

public class SynchCell2 {
    private int value;
    private boolean available;
    private Lock mutex;
    private Condition isAvail;

    public SynchCell2() {
        available = false;
        mutex = new ReentrantLock();
        isAvail = mutex.newCondition();
    }

    public void set(int v) {
        try {
            mutex.lock();
            value = v;
            available = true;
            isAvail.signalAll();
        } finally {
            mutex.unlock();
        }
    }

    public int get() {
        try {
            mutex.lock();
            if (!available) {
                try { isAvail.await(); } catch (InterruptedException ex) {}
            }
            return value;
        } finally {
            mutex.unlock();
        }
    }
}

Note importanti: i metodi non sono synchronized; il lock e gestito esplicitamente con lock()/unlock(); e essenziale il blocco finally per garantire il rilascio anche in caso di eccezioni; Condition offre await(), signal(), signalAll() al posto di wait/notify/notifyAll.

BoundedBuffer con condition variabili multiple

Con ReentrantLock possiamo finalmente avere notFull e notEmpty come variabili di condizione separate, permettendo segnalazioni selettive:

Notate: nel put si fa notEmpty.signal() (sveglia solo consumatori), nel get si fa notFull.signal() (sveglia solo produttori). Nessun overhead di risvegli inutili!

9. Semantica di segnalazione in Java: effetti indesiderati

Il professore ha dedicato una parte significativa della lezione a mostrare un comportamento curioso della semantica di segnalazione in Java. La semantica adottata (Signal-and-Continue con E = W < S) puo portare a ordini di esecuzione controintuitivi.

Scenario dimostrativo: Tre thread competono per un monitor. T1 entra, esegue await() e si sospende. T2 entra, esegue signal() e rilascia. T3 sta aspettando di entrare. Chi ottiene il lock dopo T2?

Con E = W < S, il thread risvegliato (T1) compete ad armi pari con i thread in attesa sul lock (T3). Dipende dall'implementazione della JVM e dallo scheduler del sistema operativo chi entra per primo. Nell'esempio mostrato, T3 e entrato prima di T1.

OUTPUT OSSERVATO:
First thread started.
First thread inside, going to wait
Second thread started.
Second thread inside
Third thread started.
Second thread inside, going to signal
Second thread inside signaled.
Third thread inside.            // <- T3 entra prima di T1!
First thread unblocked.         // <- T1 solo dopo T3
Attenzione

Questo comportamento non e un bug di Java, ma una conseguenza della semantica S&C. Non bisogna mai assumere che dopo un signal il thread risvegliato sia il prossimo a eseguire. E per questo che tutte le attese devono essere in ciclo while (non if) e la logica deve essere robusta indipendentemente dall'ordine di ripresa.

Errore comune: Confondere wait/notify (meccanismi di base su Object) con await/signal (da Condition). Il professore ha mostrato il caso in cui, usando ReentrantLock, si chiama erroneamente wait() invece di await() o notify invece di signal, cosa che causa IllegalMonitorStateException o comportamenti errati.

10. Monitor e semafori: equivalenza espressiva

I monitor possono essere realizzati usando semafori come building blocks di livello piu basso. Per ogni monitor servono:

Le due discipline si implementano cosi:

Prologo per ogni operazione:
  wait(mutex)
Epilogo per ogni operazione:
  signal(mutex)

waitC(cond) =
  condcount++;
  signal(mutex);     { rilascia il lock del monitor }
  wait(condsem);     { si blocca sulla condizione }
  wait(mutex);       { quando si risveglia, riacquisisce il lock }

signalC(cond) =
  if (condcount > 0) {
    condcount--;
    signal(condsem); { sblocca un processo in attesa }
  }
Prologo per ogni operazione:
  wait(mutex)
Epilogo per ogni operazione:
  signal(mutex)

waitC(cond) =
  condcount++;
  signal(mutex);
  wait(condsem);     { si blocca — verra sbloccato da signalC }

signalC(cond) =
  if (condcount > 0) {
    condcount--;
    signal(condsem); { sblocca il processo in attesa }
    wait(mutex);     { il signaller si mette in attesa del lock }
  }

Implementare un semaforo con un monitor

Chiudendo il cerchio, si puo anche implementare un semaforo usando un monitor, dimostrando l'equivalenza espressiva dei due costrutti:

monitor Semaphore {
  integer s := <InitValue>
  cond notZero;

  procedure wait() {
    if s = 0
      waitC(notZero);
    s := s - 1;
  }

  procedure signal() {
    s := s + 1;
    signalC(notZero);
  }
}

Il professore mostra anche una versione alternativa che separa il caso in cui s > 0 (decrementa senza bloccare) dal caso s = 0 (si blocca). Entrambe le versioni sono corrette. Potete esplorarla riga per riga qui sotto:

Nota del redattore I monitor consentono di costruire componenti di coordinamento riutilizzabili: latch, barriere, rendez-vous, message box, blackboard, event services. Sono un tassello fondamentale nella progettazione di sistemi concorrenti perche separano la politica di sincronizzazione dalla logica applicativa.

11. Confronto: semafori vs variabili di condizione

Il professore dedica una slide specifica al confronto, che e importante per evitare confusioni:

SemaforoVariabile di condizione (nei monitor)
wait(S) puo non bloccare (se S.V > 0)waitC(cond) blocca sempre (rilascia il lock e si sospende)
signal(S) ha sempre effetto (incrementa S.V o sblocca un processo)signalC(cond) non ha effetto se la coda e vuota
signal(S) sblocca un processo arbitrario (nei semafori deboli)signalC(cond) sblocca il processo in testa alla coda FIFO
Il processo sbloccato da signal puo riprendere immediatamenteIl processo sbloccato da signalC deve attendere che il processo segnalante esca dal monitor (a seconda della disciplina di segnalazione)
Per l'esame Domanda classica: "Qual e la differenza tra semafori e variabili di condizione?" Rispondete con questa tabella. Aggiungete che i semafori sono un meccanismo di coordinamento esterno ai dati, mentre le variabili di condizione operano all'interno del monitor e sono strettamente legate allo stato incapsulato.

Verifica le tue conoscenze

Qual e la differenza fondamentale tra wait su un semaforo e waitC su una variabile di condizione?

wait(S) su semaforo puo non bloccare (se S.V > 0 si limita a decrementare). waitC(cond) su variabile di condizione blocca sempre e rilascia il lock del monitor. Inoltre, signal su semaforo ha sempre effetto (incrementa o sblocca), mentre signalC non ha effetto se la coda e vuota.

Quali sono le quattro condizioni necessarie per il deadlock secondo Coffman?

Mutua esclusione, hold and wait, no preemption, circular wait. Devono valere tutte simultaneamente. Rimuovendone anche solo una, il deadlock e impossibile.

Perche in Java si deve sempre usare while e non if per il controllo della condizione dopo wait()?

Per due ragioni: (1) spurious wakeup — un thread puo svegliarsi senza notify/interrupt/timeout; (2) signaling semantics — in Java (S&C) il thread risvegliato deve competere per il lock e potrebbe passare del tempo prima di riottenere l'esecuzione, durante il quale la condizione potrebbe cambiare. Il ciclo while garantisce che la condizione sia riverificata prima di proseguire.

Cosa significa che un semaforo soddisfa l'invariante S.V = k + #signal(S) - #wait(S)?

Significa che il valore corrente del semaforo e uguale al valore iniziale piu il numero di signal eseguiti meno il numero di wait completati (quelli che non hanno bloccato). Questo invariante formalizza il comportamento del semaforo ed e la base per dimostrare la correttezza dei programmi che lo usano.

Qual e la differenza tra safety e liveness? Fate un esempio di ciascuna per il problema dei Filosofi.

Safety: "le cose brutte non accadono mai" — es. "nessun filosofo mangia con un solo forchetta in mano" (invarianza). Si esprime con . Liveness: "le cose buone prima o poi accadono" — es. "ogni filosofo che ha fame prima o poi mangia" (assenza di starvation). Si esprime con .

Perche con un semaforo weak la soluzione della sezione critica per N processi non garantisce assenza di starvation?

Nei semafori weak, S.L e un insieme — quando si fa signal, il processo sbloccato e scelto arbitrariamente. Un processo potrebbe non essere mai selezionato se lo scheduler e sfortunato (o se altri processi continuano a entrare e uscire dalla CS). Nei semafori strong (coda FIFO), l'ordine di arrivo garantisce che ogni processo prima o poi venga servito.