Articolo pubblicato sul n. 107 di
MCmicrocomputer
(Edizioni
Technimedia Srl - Roma) nel maggio 1991
Multitasking:
Cooperazione ad
ambiente locale
di Andrea de Prisco
Siano finalmente giunti alla puntata "centrale" della
rubrica Multitasking. Da questo numero in poi, la nostra
attenzione sara' focalizzata sui meccanismi di comunicazione
a "scambio messaggi", propria della cooperazione ad ambiente
locale, ma spesso presente anche in linguaggi di
programmazione paralleli piu' propriamente ad ambiente
globale.
La netta distinzione
Nelle puntate precedenti abbiamo mostrato alcuni meccanismi
di cooperazione-sincronizzazione basati sull'utilizzo
(arbitrato) di strutture dati condivise. La condivisione di
dati da parte di piu' processi paralleli e' tipica della
cooperazione ad ambiente globale. E' proprio all'interno di
questo che sono presenti le strutture comuni, e un certo
numero di meccanismi messi a disposizione dal sistema
permette di accedervi in maniera mutuamente esclusiva.
Abbiamo cosi' parlato di sospensione del multitasking,
operazioni di Lock-UnLock, semafori, monitor: tutte con
l'unico scopo di scongiurare disastrose collisioni tra
processi paralleli.
A partire da questo numero, se volete, potete anche
dimenticare tutto quanto narratovi (o quasi). Infatti nella
cooperazione ad ambiente locale non esiste alcuna risorsa (o
struttura) condivisa, ma tutto e' regolato dai processi
stessi. Processi, naturalmente, in grado di comunicare, ma
sicuramente meno inclini a fare danni "in giro per il
sistema". Cio' significa, tra l'altro, che non esistendo
piu' strutture dati e risorse condivise, queste sono sempre
e comunque inglobate all'interno di processi. Tanto per fare
subito un esempio, se due o piu' processi hanno bisogno di
un buffer per effettuare operazioni di inserimento ed
estrazione di oggetti, nel caso "ambiente globale" tale
buffer sarebbe stata una risorsa condivisa il cui accesso
viene regolato dalle solite P e V o da un monitor; nel caso
"ambiente locale" e' necessario creare un processo che al
suo interno ha come struttura privata il buffer e tramite
scambio messaggi con gli altri processi effettua inserimenti
ed estrazioni di oggetti (figura 1).
Comunicazioni inter process
In figura 2 e' mostrata schematicamente una coppia di
processi in grado di comunicare tra loro. E' il caso piu'
semplice: abbiamo un processo A che produce dati da inviare
a B che li utilizza. Sempre in figura 2 la freccia che
collega i due processi e' il canale di comunicazione tra
questi. E non e', come potrebbe sembrare, una struttura
condivisa dai due processi in quanto a livello di questi il
canale non e' visibile. E' si' un oggetto condiviso, ma ad
un livello piu' basso: al livello del nucleo del sistema
operativo. Infatti tanto A quanto B non hanno visione del
canale ma della comunicazione in una forma piu' astratta. A
manda messaggi a B, B riceve messaggi da A: che ci sia di
mezzo un canale lo sappiano noi e il sistema operativo, ma
non i processi. Questo, naturalmente, quando la cooperazione
e' realmente ad ambiente locale: in realta' il piu' delle
volte i linguaggi di programmazione parallela non sono cosi'
"puliti" fin in fondo e spesso lasciano visione all'interno
dei processi anche di oggetti non meglio identificati ma
comunque, almeno per certi versi, condivisi. Ma non
scendiamo troppo nei dettagli subito: avremo modo di
approfondire meglio l'argomento in seguito.
Dicevamo che il canale e' un oggetto che permette la
comunicazione tra processi ma non e' direttamente visibile
da questi. Un po' come il Program Counter nella
programmazione in assembler (quando questo non e' allocato
in un normale registro di macchina, ndr) o i vari puntatori
alle aree dati nei linguaggi di programmazione ad alto
livello.
Il canale e' dunque direttamente (nonche' esclusivamente)
utilizzato dal nucleo del sistema operativo per effettuare
la comunicazione.
Cio' che avviene all'interno dei processi (come in figura 2)
e' che il processo mittente ad un certo punto esegue una
operazione di "send" per spedire un messaggio (ad esempio un
dato) al processo B. Questo, per ricevere l'informazione in
arrivo da A, analogamente eseguira' la sua operazione di
comunicazione che nel caso del processo destinatario sara'
una "receive". La "send" avra' tra i suoi parametri
l'oggetto da spedire, la "receive" una variabile dello
stesso tipo nella quale ricevera' l'oggetto trasmesso. In
pratica a trasmissione avvenuta l'effetto finale sara'
quello di assegnare alla variabile indicata nella funzione "receive"
del processo destinatario il valore dell'oggetto trasmesso
dal mittente e indicato nella funzione "send".
Il canale, conseguentemente, eredita il tipo dell'oggetto
trasmesso che come abbiamo detto e' anche uguale a quello
della variabile targa del processo destinatario.
Ma per definire un canale non basta indicare il suo tipo ma
occorre in qualche modo definire anche mittente e
destinatario della comunicazione. Esistono fondamentalmente
due strade: canali con nomi espliciti dei processi (figura
3a) e canali con porte (figira 3b). Ricordando che tale
definizione non spetta ne' al processo mittende ne' al
processo destinatario di una comunicazione ma semplicemente
al sistema operativo che deve avere un "quadro chiaro" di
tutta la situazione, e' al momento della compilazione dei
vari processi che sono definiti implicitamente anche i vari
canali.
Nel caso di canali con nomi espliciti dei moduli il canale
e' definito dalla tripla ordinata:
(Mittente, Destinatario, Tipo)
e viene dedotta dal sistema in base alle operazioni di "send"
presenti nei processi mittenti e alle operazioni di "receive"
nei processi destinatari che contengono come primo parametro
il nome del processo partner. Se ad esempio nel processo A
troviamo:
send(B, messaggio)
nel processo B:
receive(A, variabile)
e tanto "messaggio" quanto "variabile" sono dello stesso
tipo T, il canale e' definito dalla tripa:
(A, B, T)
Nel caso di canali con porte il canale e' definito dalla
coppia ordinata:
(PortaMittente, PortaDestinatario)
Le porte sono oggetti privati dei processi e rappresentano
delle interfacce logiche tra i processi e i canali di
comunicazione. Hanno lo stesso tipo del messaggio da
trasmettere e quindi del canale che interfacciano.
Nelle operazioni di "send" presenti nei processi mittenti e
nelle operazioni di "receive" nei processi destinatari si fa
esplicito riferimento non ai processi partner ma alla
propria porta di ingresso o di uscita precedentemente
definita.
Cio' che e' ora necessario e' "linkare", prima
dell'utilizzo, una porta con un processo, sia nel caso del
mittente (che linka la sua porta al nome del processo
destinatario) che nel caso del destinatario (che linka la
sua porta al nome del processo mittente).
Se ad esempio nel processo A troviamo:
out port PA: T
var messaggio: T
bind(PA, B)
send(PA, messaggio)
nel processo B:
in port PB: T
var variabile: T
bind(PB, A)
receive(PB, variabile)
il canale e' definito dalla coppia:
(PA, PB)
e non e' piu' presente il tipo in quanto gia' definito nella
dichiarazione delle porte.
Comunicazioni sincrone e asincrone
C'e' un particolare che abbiamo volutamente sorvolato fino
ad ora: la sincronia o asincronia delle comunicazioni. Cosa
succede, per dirla in breve, se, quando il mittende effettua
una "send", il destinatario non effettua la corrispondente "receive"
perche' momentaneamente indaffarato in altri lavori? Il
mittente deposita il messaggio e continua oppure attende
pazientemente il destinatario per consegnare personalmente
il messaggio? Esistono in genere tutt'e due le possibilita':
e, naturalmente, non esiste la soluzione migliore, ma
possono essere utili l'una o l'altra possibilita' a seconda
dei casi.
E' chiaro, inoltre, che la comunicazione asincrona e'
possibile solo associando al canale un'area di memoria dove
immagazzinare i messaggi spediti e non ancora letti.
Discorso del tutto analogo per le operazioni di "receive".
Cosa deve fare il destinatario se un messaggio richiesto non
e' stato ancora spedito? Resta li' impalato ad aspettare
oppure continua per la sua strada senza il messaggio
richiesto? Come nel caso precedente puo' essere utile
disporre di entrambe le soluzioni a seconda dei casi. Nei
prossimi numeri, quando cominceremo a mostrare alcuni
programmi multitask vedremo come utilizare a seconda dei
casi le varie chance disponibili.
Forme di comunicazione
In figura 4 sono mostrate le forme di comunicazione base. La
prima, la piu' semplice, prevede semplicemente che vi sia
nella comunicazione un solo mittente e un solo destinatario
(comunicazione simmetrica). E' il caso che abbiamo gia'
utilizzato negli esempi precedenti in cui un processo invia
e un processo riceve attraverso un canale. Naturalmente e'
possibile instaurare una nuova comunicazione anche dal
processo destinatario verso il mittente (invertendo i ruoli)
attraverso un nuovo canale diverso dal primo. Infatti, posto
che il tipo dei dati che i due processo si scambiano
vicendevolmente e' lo stesso, i due canali sono diversi
essendo differenti il mittente e il destinatario nei due
casi. Il primo canale sara' ad esempio identificato dalla
terna:
(A, B, T)
il secondo dalla terna:
(B, A, T)
Le forme di comunicazione asimmetriche (rappresentate in
figura 4b e 4c) consentono rispettivamente l'esistenza di
piu' mittenti o piu' destinatari sul medesimo canale. La
comunicazione asimmetrica in uscita (figura 4c) e' detta
anche comunicazione per diffusione in quanto il medesimo
messaggio e' inviato simultaneamente a piu' destinatari.
Nel primo caso della comunicazione asimmetrica in ingresso,
si tratta di messaggi diversi (provenienti da processi
diversi) che si accodano sul canale e che verranno letti in
istanti successivi dal processo destinatario. Tanto i
mittenti di figura 4b quanto di destinatari di figura 4c non
sono a conoscenza dell'asimmetria del canale: per loro la
comunicazione e' sempre e comunque simmetrica (il loro
partner nella comunicazione e' unico). Vice versa il
destinatario di una comunicazione asimmetrica in ingresso e
il mittente di una comunicazione asimmetrica in uscita sono
coscienti della asimmetria in quanto il primo dovra'
esplicitamente indicare da quali processi (piu' d'uno)
ricevere il secondo a quali processi spedire.
Il nondeterminismo
Se fino ad oggi pensavate che gli attuali calcolatori
fossero delle macchine deterministiche avete una visione
rigidamente sequenziale dei calcolatori. Oggi, fermo
restando la determinicita' di un singolo processo (che ad
ogni sequenza di input fornisce sempre un'unica sequenza di
output), le cose stanno ben diversamente nelle macchine
multitasking. Infatti a livello di processi nessuna ipotesi
e' fatta sull'avanzamento parallelo di questi, ne'
sull'indeterminatezza di alcune situazioni che possono
verificarsi. L'esempio tipico e' mostrato proprio in figura
4b: i processi A1, A2, A3 sono assolutamente indipendenti;
utilizzano (senza saperlo) lo stesso canale verso B il quale
riceve sequenzialmente i messaggi dai tre processi mittenti.
Se nello stesso istante logico piu' processi inviano
messaggi a B non e' definito a priori in quale sequenza
questi saranno recapitati al destinatario. Ne' B potra' fare
nulla per forzare una sua preferenza o priorita'.
E se in alcuni casi possiamo procedere incuranti del
problema, in altri potrebbe essere necessario pilotare in
qualche modo la scelta del messaggio da ricevere per primo.
Naturalmente in questo caso non avremo piu' un unico canale
asimmetrico ma tanti canali simmetrici quanti sono i
mittenti (figura 5). All'interno del destinatario la receive
avra' come parametri tante triple quanti sono i mittenti.
Ogni tripla sara' formata da una variabile targa (nella
quale, eventualmente, ricevere il messaggio), il nome del
mittente e una variabile di condizionamento (ad esempio
un'espressione logica) che maschera o meno la lettura da
quel canale. Cosi' se abbiamo 5 canali e altrettanti
processi che spediscono e vogliamo ricevere solo dai primi
due processi sara' sufficiente far valere TRUE i primi due
parametri booleani e FALSE i rimanenti tre.
Niente paura
Certo, per chi ha utilizzato per proprie risorse di calcolo
sempre in maniera sequenziale questo linguaggio potra'
sembrare anche un tantino ostico, ma per fortuna una volta
presa la mano tutto torna ad essere semplice e magicamente
affascinante.
Tra gli utenti dei piccoli sistemi i piu' fortunati sono gli
utenti Amiga che dispongono di un vero e proprio computer
multitask con tanto di processi, messaggi, porte, risorse,
anche se non sempre gestiti o gestibili nel piu' pulito dei
modi.
Gli utenti MS-DOS per giocare "al multitasking" hanno invece
una possibilita' diversa, molto piu' interessante, anche se
poco proponibile dal punto di vista economico: acquistare
una scheda per il loro PC dotata di almeno un transputer e
due mega di ram e utilizzare su questa il linguaggio OCCAM.
Cosi' come faremo noi a partire dal prossimo numero:
metteremo un po' il naso nel multitask che ci circonda per
spulciare insieme le cose piu' interessanti dal punto di
vista didattico e applicativo. Inutile ricordarvi che sono,
come sempre, ben accette tutte le collaborazioni dei lettori
che perverranno in redazione. Per il momento buon lavoro e
appuntamento al prossimo mese.
Impaginato
originale...
Articolo pubblicato
su
www.digiTANTO.it - per ulteriori informazioni
clicca qui
|