Sezione critica
![Niente fonti!](http://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Question_book-4.svg/45px-Question_book-4.svg.png)
In informatica, una sezione critica, anche detta regione critica, è una porzione di codice che accede a una risorsa condivisa tra più flussi di esecuzione di un sistema concorrente.
Motivazione
Quando flussi diversi accedono a una risorsa comune in tempi diversi, ognuno di essi può completare la propria operazione (lettura o scrittura) senza essere "disturbato" dagli altri. Se questa condizione vale sempre per una certa risorsa, allora si dice che la lettura e la scrittura su quella risorsa sono operazioni atomiche: ogni flusso può intervenire solo prima o dopo che l'altro abbia completato la propria operazione, e non può interromperla. Il termine "atomiche" è inteso nella sua accezione di "unitarie", "indivisibili". Se tutti gli accessi a una risorsa sono atomici, allora non ci sono problemi di sincronizzazione.
Per esempio, un programma per la copia di file potrebbe essere implementato con due thread: uno che copia i byte dal disco di origine verso la memoria RAM, e l'altro che contemporaneamente preleva i byte dalla memoria e li scrive nel disco di destinazione. Questo programma ottiene un vantaggio in velocità, perché sovrappone tra loro i tempi di latenza per l'accesso ai dischi, in modo che nessuno dei due risulti bloccato (quindi inutilizzato) a causa dell'altro.
Tuttavia, il sistema operativo non ha consapevolezza del fatto che i flussi di esecuzione si scambino dei byte: invece, vede solo letture e scritture su dischi e in memoria, e al massimo può garantire che queste operazioni siano singolarmente atomiche. Se i due thread non prevedono la presenza di sezioni critiche, può capitare che essi intervengano contemporaneamente sulla memoria comune, e in particolare può capitare che uno scriva una parte dei dati mentre l'altro li sta ancora leggendo. Il risultato è che il thread che legge "vede" un contenuto in memoria aggiornato solo parzialmente, e quindi legge informazioni errate. Ciò cambia il comportamento del programma in modo imprevedibile, perché il programmatore non può sapere con quali tempistiche saranno eseguiti i due flussi a tempo di esecuzione.
La soluzione per risolvere il problema è vincolare i due thread ad accedere alla risorsa condivisa sempre e solo in momenti diversi. In gergo, si dice che gli accessi alla risorsa (in lettura e/o in scrittura) si trovano all'interno di sezioni critiche.
Implementazione
Il problema della sezione critica si affronta progettando un protocollo che i processi possano usare per cooperare: ogni flusso di esecuzione deve chiedere il permesso per entrare nella propria sezione critica e la sezione di codice che realizza questa richiesta è la sezione d'ingresso; la sezione critica può essere seguita da una sezione d'uscita e la restante parte del codice è detta sezione non critica.
Il modo per definire una sezione critica dipende dal linguaggio di programmazione in uso, tuttavia il meccanismo è generalmente lo stesso: si richiama un meccanismo di sincronizzazione (ad esempio un semaforo) all'entrata e all'uscita del codice che si vuole nella sezione critica. All'entrata, questo meccanismo controlla il semaforo, per verificare che non vi sia nessun altro processo che sta eseguendo il codice critico. In caso affermativo, l'esecuzione prosegue e il processo corrente prende possesso del semaforo (e quindi della sezione critica), per rilasciarlo all'uscita. In caso contrario, il processo attende che il semaforo si liberi, oppure, nel frattempo, può eseguire un altro compito. L'accesso di più processi ad una sezione critica ha, normalmente, una priorità regolata da una coda FIFO.
Le sezioni critiche sono implementate tipicamente invocando primitive di sistema. Lo svantaggio è che le API di queste primitive dipendono appunto dal sistema, quindi il codice scritto risulta non portabile e va riscritto per i diversi sistemi. Alcuni linguaggi risolvono questo problema fornendo una sintassi unificata per la definizione delle sezioni critiche, in modo da evitare il ricorso a funzioni di sistema (per es. Java).
Il motivo per cui si definiscono sezioni critiche è permettere l'accesso ad una risorsa in mutua esclusione, da parte dei thread che richiedono di leggere o scrivere su tale risorsa. Se viene permesso di accedere alla sezione critica ad un solo processo per volta, allora c'è la garanzia che la risorsa condivisa non venga mai modificata da più processi contemporaneamente, salvando così la coerenza delle informazioni che contiene.
Altre informazioni
Le sezioni critiche non servono quando il contenuto di una risorsa condivisa è immutabile, ovvero quando nessun flusso di esecuzione è autorizzato a scrivere su quella risorsa.
Infatti, nella programmazione ad oggetti, è noto che gli oggetti immutabili (cioè gli oggetti progettati in modo che non sia consentito cambiarne lo stato interno una volta creati) sono thread-safe, cioè possono essere utilizzati da più flussi di esecuzione senza necessità di ricorrere alle sezioni critiche. Fare uso di oggetti immutabili dovunque possibile rende il codice che li usa più semplice da scrivere e da leggere.[1]
Nella gestione della sezione critica, le garanzie principali da fornire sono tre:
- mutua esclusione: se il processo Pi è in esecuzione nella sua sezione critica, nessun altro processo può essere in esecuzione nella propria sezione critica.
- attesa limitata: se un processo ha già richiesto l'ingresso nella sua sezione critica, esiste un limite al numero di volte che si consente ad altri processi di entrare nelle rispettive sezioni critiche prima che si accordi la richiesta del primo processo. (limite massimo di attesa dopo che ho espresso la volontà di entrare in sezione critica, nessun deadlock né starvation)
- progresso: se nessun processo è in esecuzione nella sua sezione critica, solo i processi che desiderano entrare nella propria possono partecipare alla decisione su chi sarà il prossimo ad entrare in sezione critica e tale decisione non può essere ritardata indefinitamente.
La gestione delle sezioni critiche nei sistemi operativi prevede l'impiego di due strategie principali:
- Kernel con diritto di prelazione (pre-emptive)
- Kernel senza diritto di prelazione (non pre-emptive)
Un kernel con diritto di prelazione consente che un processo funzionante in modalità di sistema, sia sottoposto a prelazione, rinviandone in tal modo l'esecuzione.
Note
- ^ Joshua Bloch, Effective Java, 2ª ed., Pearson Informatica, ISBN 978-88-7192-481-6.«Tema 15: Gli oggetti immutabili sono thread-safe in quanto tali e non richiedono alcuna sincronizzazione.»
![](http://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Computer_n_screen.svg/24px-Computer_n_screen.svg.png)