La Torre di BaBasic - 7
La Torre di BaBasic - 7
In questa puntata vedremo come i computer, invece, non condividano i limiti della nonna.
Una delle tematiche più complesse e meglio studiate della Computer science, infatti, è proprio quella della multiprogrammazione, ovvero i metodi utilizzati per far eseguire simultaneamente più compiti ad un solo computer. Questo è spiegabile alla luce della storia: sino a pochissimi anni or sono un computer era un oggetto costosissimo che andava sfruttato all'osso per ripagarne la spesa.
Le soluzioni proposte rispondono alle difficoltà che si incontrano volendo far girare più programmi, appartenenti a diversi utenti, contemporaneamente. Per il lettore di Bit, che ha quasi sempre a che fare con un personal computer interamente dedicato, il problema diviene più interessante quando spostiamo la nostra attenzione su una variante di questo dilemma: vogliamo indagare la possibilità per un computer di utilizzare più applicazioni appartenenti allo stesso utente. Ognuno di noi perde regolarmente buona parte delle proprie chances di guadagnare un posto in paradiso quando viene confrontato con un computer che seraficamente presenta per diverse ore il ferale messaggio "Attendi, sono occupato con la stampa del documento"; in quei casi vengono solitamente scomodati interi pantheon... Un problema analogo è di esclusivo retaggio dei programmatori, che sono costretti ad attendere passivamente davanti allo schermo per qualche minuto mentre il compilatore del linguaggio prescelto macina le righe del loro programma.
Com'è abitudine per questa rubrica, ci occuperemo delle questioni relative alla programmazione: che problemi si presentano a chi volesse scrivere un programma che sfrutti le capacità multiprogrammate di un computer? La questione è tutt'altro che accademica, dato il recente fiorire di sistemi operativi per personal computer che, chi più chi meno - quasi tutti meno - permettono questa modalità d'operazione.
Per i programmatori che utilizzano un sistema operativo multiprogrammato (in inglese, multitask), non è assolutamente necessario conoscere tutte le tematiche, i problemi e le soluzioni connesse: normalmente il sistema operativo fa del suo meglio per operare in modo invisibile (in gergo si preferisce dire "trasparente") al programma e pertanto anche al programmatore. E alloracui prodest , potrebbe osservare qualcuno che non abbia totalmente dimenticato il latino, a chi giova la conoscenza degli arcani che stiamo per svelare? Non dobbiamo dimenticare che un sistema operativo è a sua volta un programma, e a qualche poveretto toccherà pure di scriverlo! Inoltre, il programmatore conscio dei pericoli e delle risorse disponibili sotto un sistema multitask possiede molte frecce in più al suo arco. Ed infine, anche se tutto il nostro discorso restasse inutilizzabile ed inutilizzato, avremo soddisfatto un poco la nostra curiosità.
Immaginiamo di entrare in una banca, e di osservare una scenetta dove due cassieri stanno incassando degli assegni dai clienti, pagando loro gli importi. Ciascun cassiere ha a disposizione il terminale di un computer (per il nostro esempio può bastare una tastiera, come vedremo) e deve battere la cifra appena pagata in modo che il computer tenga il conto di quanti soldi sono stati riscossi dai clienti.
Il computer sta facendo girare contemporaneamente due programmi, i quali ottengono dalle rispettive tastiere la cifra che il cassiere ha appena dato al cliente e sommano quella cifra al totale precedente.
Totale := Totale + Cifra Riscossa;
Quindi, se i due cassieri hanno sinora pagato trenta milioni ed il cassiere A paga al suo cliente un milione, il totale viene aggiornato a trentuno.
Dove sta il problema, dira qualcuno che magari non è neppure ragioniere? Il problema sta nel fatto che il totale - la variabile che memorizza il totale, dunque - è condivisa tra i due programmi, e questo sta per far passare un grosso guaio ai nostri amici bancari.
Il semplicissimo programma che abbiamo visto viene eseguito dal computer nel modo che state per leggere, dove con "leggi" intendiamo "accedi in lettura alla variabile che memorizza il valore di":
Leggi il Totale
Leggi la Cifra
Sommali
Scrivi il risultato come nuovo totale
Adesso vediamo cosa può succedere quando i due cassieri stanno pagando contemporaneamente due clienti. Immaginiamo che il totale fosse in precedenza di 30 milioni.
- Il programma uno legge il totale: 30 milioni
- Il programma due legge il totale: 30 milioni
- Il programma uno legge la cifra: 1 milione
- Il programma due legge la cifra: 2 milioni
- La prima somma viene valutata in 31 milioni
- La seconda somma viene calcolata in 32 milioni
- Il primo programma scrive "31" a totale
- Il secondo programma sovrascrive "32"
Se ci pensiamo un poco, notiamo subito che il problema sta nell'accesso in scrittura alla variabile condivisa. E' in quel punto che si verifica l'errore.
In generale, si può concedere a un numero grande a piacere di programmi di leggere i dati delle variabili condivise, ma è necessario che un solo programma possa scrivere un nuovo valore nella variabile, mentre gli altri programmi aspettano. Per questo motivo, la zona del programma dove si accede in scrittura alla variabile viene solitamente indicata con il termine di "zona critica".
La soluzione classica al problema della condivisione delle variabili utilizza dei costrutti logici conosciuti come 'semafori' (semaphores). I semafori furono inventati da Dijkstra, noto guru dell'informatica, e sono concettualmente abbastanza semplici: davanti al semaforo si formano delle code di programmi che aspettano il loro turno per accedere alla variabile condivisa. Ogni programma, quando sta per entrare nella zona critica (che portemmopensare come l'incrocio stradale protetto dai semafori) da un'occhiata al semaforo, e se scopre in questo modo che la variabile è già usata da un altro programma si ferma e... aspetta il verde.
I semafori sono realizzati come due procedure, contenute nel sistema operativo, e che vanno chiamate dal programma. Nella classica teoria informatica le procedure che realizzano i semafori hanno, per motivi storici, i nomi alquanto criptici di P e V. Se proviamo a chiamarle EntraIncrocio ed EsciIncrocio le cose sembrano subito più semplici (trovate una spiegazione delle due procedure nella figura 1).
------------------------------------------------------------------------
Figura 1: Le procedure semaforiche P e V
procedura EntraIncrocio (semaforo);
semaforo: variabile statica integer;
begin
if semaforo > 0 then semaforo := semaforo - 1;
else sospendi il programma corrente;
end;
Procedure EsciIncrocio (semaforo);
semaforo: variabile statica integer;
begin
if c'è almeno un processo sospeso davanti al semaforo
then sveglia il [primo] processo in coda
else semaforo := semaforo + 1;
end;
Per comprendere perfettamente come funzionano i semafori, provate a pensare a cosa succede al nostro esempio bancario, quando il programma usato dai due cassieri viene riscritto come:
- Semaforo := 0;
- ripeti per tutto il giorno:
- Leggi dalla tastiera la cifra;
- Entraincrocio (semaforo);
- Leggi il totale;
- Somma totale e cifra;
- Scrivi nuovo totale;
- EsciIncrocio (semaforo);
Un costrutto discretamente più potente dei semafori è chiamato Monitor, ed è stato proposto dal solito Dijkstra, e poi messi a punto da tale Brinch Hansen che ne aveva bisogno per preparare il suo linguaggio chiamato Concurrent Pascal.
I semafori funzionano perfettamente, ma hanno il problema di essere piuttosto primitivi: qualche volta è difficile esprimere una soluzione a un complesso problema di concorrenza utilizzando operazioni semplici come quelle semaforiche. E c'è anche un altro problema da non sottovalutare: se c'è un errore in un programma qualunque, e questo si mette ad usare indiscriminatamente le procedure semaforiche, è praticamente certo che il sistema diviene instabile, e finisce per crollare: travolgendo nella propria caduta anche i programmi corretti, il che è molto poco piacevole.
Il principio logico sul quale si basa il monitor prevede che esista una zona critica composta sia da codice che da variabili. Possiamo facilmente vedere in questa raffigurazione un tipico problema informatico: l'uso di basi di dati (data bases) dove i dati possono venire accessi contemporaneamente da più programmi contemporaneamente.
Tanto per fare un esempio, proprio due giorni fa (per chi scrive) il sottoscritto stava tornando in Italia dagli Stati Uniti. Sul Boeing 747 si è assistito ad un episodio pochissimo edificante: quattro passeggeri hanno scoperto che il computer addetto alle prenotazioni dei posti aveva loro assegnato il medesimo sedile. Questo è un caso esemplare di un errore nella gestione di una base di dati (l'elenco dei posti assegnati) da parte di un programma che viaggia in multitasking (infatti ciascuna console alla quale lavora un operatore che vende i biglietti ha normalmente assegnato un programma, ed esiste un programma del genere per ciascun operatore).
Possiamo immaginare il monitor come un contenitore, entro il quale si trovano i dati e le procedure condivise dai programmi (nel caso di una base di dati, le variabili memorizzano la base di dati e le procedure consentono la ricerca, l'inserimento e la cancellazione di un dato). In questa stanza immaginaria esistono più porte d'ingresso, e una porta d'uscita. Le porte d'ingresso corrispondono ad altrettante procedure, che vanno tipicamente sotto il nome poco originale di procedure entry. Un programma che deve utilizzare il monitor bussa ad una delle porte, corrispondente all'operazione che esso disidera effettuare. Se all'interno del monitor si trova un altro programma, il monitor è occupato, e il nostro programma si mette in paziente attesa davanti alla porta.
Probabilmente a qualche lettore sarà venuto in mente un esempio nella vita di tutti i giorni che rispecchia perfettamente questa situazione. Effettivamente le cose funzionano in quel modo, ma per pudore evitiamo di esplicitare l'esempio...
Notate che c'è una consistente differenza rispetto alle primitive semaforiche: in quel caso esiste una sola coda, mentre nel caso del monitor si forma una coda per ciascuna porta d'ingresso.
Il monitor può venire ulteriormente complicato se immaginiamo che alcune procedure entry non debbano necessariamente venire eseguite in assoluta solitudine dal programma (cioé, non accedono in scrittura alle variabili condivise: per esempio, se l'operatore che assegna i posti all'aeroporto deve semplicemente verificare che esistano posti liberi su un volo, questo non significa che un altro operatore che sta vendendo un biglietto debba venire bloccato).
Un altra complicazione concettualmente più complessa riguarda la possibilità che più programmi coesistano dentro i monitor, con uno solo di loro effettivamente all'opera sui dati. Si tratta di un caso piuttosto intricato che i lettori interessati possono trovare svelato in qualsiasi buon libro sui sistemi operativi.
Nella prossima puntata vedremo come Ada fornisca strumenti al programmatore per gestire il multitasking, in modo ancora più sofisticato di quanto permettano i monitor, utilizzando le unità di programmazione chiamate task e il meccanismo del rendez-vous tra programmi.