Articolo pubblicato sul n. 110 di MCmicrocomputer (Edizioni Technimedia Srl - Roma) nel settembre 1991

MCmicrocomputer


Multitasking:
OCCAM: canali e messaggi

di Andrea de Prisco

Dopo la doverosa introduzione del numero scorso, questo mese scenderemo un po' piu' nei dettagli riguardo le comunicazioni tra processi OCCAM. Vedremo poi come risolvere il problema della sincronicita' delle comunicazioni, mostrando un primo esempio di programma OCCAM. Naturalmente parallelo...Copertina del numero di MCmicrocomputer contenente l'articolo

Array e matrici

OCCAM mette a disposizione, purtroppo, ben pochi tipi di dato. Oltre ai gia' citati INT e BYTE esiste il tipo di dato BOOL (che puo' assume solo uno dei due valori TRUE o FALSE) e, in alcune implementazioni, anche il tipo REAL32 e REAL64 che differiscono per il numero di bit delle rispettive rappresentazioni.
Tutti i tipi di dato (compresi i canali, come vedremo) possono essere "vettorizzati" ossia utilizzati come vettori o matrici. Cio' e' possibile indicando nella dichiarazione il numero di elementi di cui e' composta la nostra sruttura accanto al tipo degli elementi stessi. Ad esempio lo dichiarazione:

[100] INT pippo:

definisce un array di interi di nome "pippo" i cui elementi sono indirizzati con pippo[0], pippo[1], . . . , pippo[99]. Analogamente possiamo definire array multidimensionali semplicemente indicando piu' dimensioni nella dichiarazione:

[50][100] BOOL pluto:

definisce una matrice 50x100 i cui elementi sono di tipo BOOL.
Sono possibili assegnamenti tra array senza ricorrere a loop, a condizione che gli array sorgente e destinatario siano dello stesso tipo (dimensione dell'array e tipo degli elementi). Se le dimensioni non coincidono (ma solo il tipo degli elementi) e' possibile effettuare assegnamenti parziali. Parti di array piu' grandi in array piu' piccoli o, viceversa, array piu' piccoli in parti di array piu' grandi. Scrivendo ad esempio:

[pippo FROM 10 FOR 50]

indichiamo di fatto i 50 elementi dell'array pippo dalla posizione 10 fino alla posizione 59. Questa indicazione e' valida tanto per la sorgente che la destinazione di un assegnamento o, piu' in generale, in qualsiasi valutazione di espressione. Facciamo un esempio. Immaginiamo di aver dichiarato anche l'array "piolo" nel seguente modo:

[50] INT piolo:

l'assegnamento:

piolo := [pippo FROM 10 FOR 50]

e' certamente lecito. Analogamente possiamo riferirci a sottoinsiemi sia come sorgente che come destinazione:

[piolo FROM 5 FOR 10] := [pippo FROM 50 FOR 10]

in questo caso i dieci elementi di pippo dalla posizione 50 alla posizione 59 sono copiati negli elementi di piolo dalla posizione 5 alla 14. L'importante e' che il sottoinsieme destinazione e il sottoinsieme sorgente abbiano pari dimensioni (ed elementi dello stesso tipo)
Per finire (i progettisti di OCCAM si sono proprio sbizzarriti con gli array) e' possibile definire una abbreviazione per indicare un determinato sottoarray di un array di partenza. Ad esempio scrivendo:

pippino IS [pippo FROM 10 FOR 50]

possiamo alternativamente accedere ai 50 elementi di pippo attraverso il nome pippino. Cosi' pippino[0] sara' pippo[10] e cosi' via fino a pippino[49] che sara' pippo[59]. In tal modo pippino e' a tutti gli effetti un array di 50 elementi di tipo INT e come tale dovra' essere trattato in ogni istruzioni di assegnamento (sia come sorgente che come destinazione) o di valutazione di espressione.

Canali con tipo

Come precedentemente anticipato, le comunicazioni tra processi OCCAM avvengono tramite scambio messaggi per mezzo di canali di comunicazione. Ricordiamo che il canale e' un mezzo logico di comunicazione sin al momento del lancio dell'applicazione parallela (quindi rimane tale anche terminata la compilazione) in cui assume aspetto fisico in due distinte forme a seconda che i processi comunicanti siano in esecuzione sullo stesso o su differenti transputer. Nel primo caso, infatti, il canale viene mappato in memoria e corrisponde, in pratica, ad un buffer "a zero posizioni" in cui il processo mittente inserisce il messaggio inviato e contemporaneamente il destinatario lo legge. Nel caso di processi in esecuzione su CPU diverse, il canale logico e' mappato sul link fisico che collega i due transputer in questione.
In ogni caso, prima di utilizzare un canale e' necessario dichiarare il suo nome e il tipo di messaggio che dovra' trasferire (quest'ultimo tipo, nella definizione del canale, e' detto "protocollo"). Ad esempio:

CHAN OF INT pippo:

definisce un canale di nome "pippo" sul quale "passano" solo ed esclusivamente interi (il protocollo e' INT).

Volendo definire canali un po' piu' complessi, e' sufficiente indicare il tipo del messaggio come nel caso appena visto per gli interi. Ad esempio se dobbiamo trasferire da un processo ad un altro array di 100 byte, scriveremo:

CHAN OF [100]BYTE NomeCanale:

Chiaramente esiste anche un meccanismo per definire canali con protocollo array di lunghezza variabile, che dovremo conoscere solo a tempo di esecuzione nel momento in cui ci accingiamo ad effettuare la comunicazione da parte del mittente e che conosceremo appena completata l'operazione da parte del destinatario del messaggio. Scrivendo:

CHAN OF INT::[]BYTE NomeCanale:

dichiariamo che il canale e' utilizzato per trasferire array di BYTE (potevano anche essere INT) di lunghezza variabile. Al momento della vera e propria send indicheremo la lunghezza. Ad esempio:

NomeCanale ! 25::pippo

spedisce l'array pippo (precedentemente dichiarato come [25]BYTE pippo). Nello stesso programma in un altro punto potremo utilizzare lo stesso canale per spedire un array di lunghezza diversa, ad esempio:

NomeCanale ! 100::pluto

in questo caso pluto e' un array di 100 byte. Conseguentemente il processo destinatario effettuera' la sua receive indicando una variabile di tipo INT nella quale ricevera' la lunghezza dell'array e un nome di un proprio array lungo almeno quanto l'array da ricevere:

NomeCanale ? Lunghezza::Array

Esistono poi delle abbreviazioni per definizioni di canali il cui tipo e' abbastanza complesso. Si tratta di definire a parte il protocollo di comunicazione e poi inserire il nome di questo nelle varie definizioni di canale necessarie.

PROTOCOL sequenza IS INT;INT;INT;INT:
CHAN OF sequenza lista:

definisce un protocollo di nome sequenza corrispondente a quattro interi "sparati" l'uno dopo l'altro e subito dopo un canale di nome lista con protocollo sequenza (appena definito). Cosi' il processo mittente potra' ad esempio eseguire:

lista ! 15; 35; 665; 455

e corrispondentemente il destinatario:

lista ? a; b; c; d

Naturalmente la sequenza di tipi indicata nella definizione di protocollo puo' anche essere dismogenea: l'importante che tanto il mittente quanto il destinatario si attengano strettamente alla definizione data prima di utilizzare il canale per le comunicazioni.


Protocolli variabili

Potrebbe essere utile dichiarare un canale di piu' tipi diversi e poi utilizzare di volta in volta a tempo di esecuzione il tipo giusto a seconda dei casi. Questo e' possibile in OCCAM grazie cosiddetto protocollo variabile. Ad esempio definiamo il protocollo variabile di nome "multi":

PROTOCOL multi
CASE
a; BOOL;INT
b; BYTE;BYTE
c; BYTE;BOOL;INT
:

e poi definiamo il canale di nome "star":

CHAN OF multi star:

Il processo mittente, per effettuare la send di un messaggio sul canale star deve indicare quale dei tre protocolli possibili intende utilizzare per quella comunicazione e poi regolarsi di conseguenza. Scrivendo ad esempio:

star ! a; TRUE; 250

utilizziamo il canale star impostando il protocollo "a" e, quindi, inviando al destinatario un valore BOOL seguito da un INT.
Il processo destinatario che utilizza il canale a protocollo variabile puo' comportarsi in due differenti modi: se conosce a priori quale dei tre tipi di messaggio arriveranno indichera' semplicemente il caso "a" nella sua receive:

star ? CASE a; x; y

dove natural,ente "x" e' una variabile BOOL e "y" una variabile "INT". Diversamente, se non conosce a priori il tipo del messaggio in arrivo puo' indicare le varie alternative:

star ? CASE
a; x; y
b; z; t
c; z; x; y

in questo modo se sul canale arriva un messaggio di protocollo "a" saranno assegnate le variabili "x" e "y", se e' di tipo "b" saranno assegnate le variabili di tipo BOOL "z" e "t", se il protocollo e' di tipo "c" saranno assegnate le variabili "z", "x" e "y".

Comunicazioni asincrone

In OCCAM tutte le comunicazioni inter process avvengono in maniera sincrona: l'attimo logico in cui un processo mittente effettua una send e' lo stesso in cui il destinatario esegue la receive.
In altre parole se uno dei due processi arriva prima all' appuntamento attendera' il rispettivo partner prima di continuare per la propria strada.


Articolo pubblicato su www.digiTANTO.it - per ulteriori informazioni clicca qui