Caricare documenti e articoli online 
INFtub.com è un sito progettato per cercare i documenti in vari tipi di file e il caricamento di articoli online.


 
Non ricordi la password?  ››  Iscriviti gratis
 

LA SEND TIPO 'CHIAMATA A PROCEDURA REMOTA'

informatica



LA SEND TIPO 'CHIAMATA A PROCEDURA REMOTA'


Prendiamo in esame il terzo ed ultimo tipo di send: la CHIAMATA A PROCEDURA REMOTA, una send a livello di linguaggio, detta anche rendez vous esteso.

In questo caso il mittente  non soltanto rimane in attesa che il destinatario riceva il messaggio, ma aspetta anche un messaggio di risposta dal destinatario. In altre parole, il mittente (che viene detto anche cliente) chiede un servizio al destinatario(servitore): gli passa dei parametri con i quali specifica il tipo di servizio desid 656d35g erato, e dei parametri di ritorno destinati a contenere i risultati. I risultati del servizio richiesto possono consistere in una semplice risposta, la quale dice in sostanza se il servizio è stato realizzato o meno, oppure possono essere delle informazioni più articolate.

In generale, dunque, i tempi di attesa saranno più lunghi rispetto a quelli della send sincrona, se è vero che il mittente deve rimanere bloccato per tutto il tempo che il ricevente impiega ad eseguire il servizio richiesto e a restituire i risultati.




A basso livello, una simile send può essere realizzata mediante una send asincrona e una receive bloccante, entrambe eseguite all'interno del cliente. In questo modo il cliente spedisce il messaggio senza bloccarsi in fase di invio, e poi si mette in attesa sulla ricezione del risultato. La 'chiamata a procedura remota', un costrutto proprio dei linguaggi di programmazione concorrente ad alto livello, ha la stessa struttura di una normale chiamata a sottoprogramma, cioè:


Call Pippo (parametri)  Send Pippo (parametri)


La similitudine con le chiamate a sottoprogramma arriva al punto che il cliente che utilizza questo costrutto desidera avere completa visibilità del servizio richiesto, usufruendone come se il servitore fosse un suo sottoprogramma, mentre ovviamente esso 'gira' in un differente contesto se non addirittura su di un'altra macchina. Per realizzare questa trasparenza si pone un problema di passaggio dei parametri.

Se ad esempio il cliente passa dei parametri di ingresso per indirizzo, questi devono essere dapprima trasformati in dati veri e propri dal kernel, e poi spediti. Si inizia con una fase di 'impacchettamento' del messaggio da parte del cliente, che memorizza i parametri di scambio inopportune locazione della propria area di memoria privata. Il cliente esegue quindi la sequenza send asincrona - receive bloccante. Ricevuto il segnale di 'ok' da parte del servitore, il compilatore concorrente ricostruisce a partire dai parametri di scambio il messaggio da inviare a quest'ultimo mediante un'operazione di 'spacchettamento' a suo favore.

Corrispondentemente, il servitore prevederà nell'ordine le seguenti operazioni:

- una receive bloccante (questa è sempre la prima istruzione del servitore, e quindi è implicito che in questo caso il destinatario resta immobile fino a che qualcuno non richiede un suo servizio);

- uno 'spacchettamento' del messaggio in arrivo da parte del kernel;

- l'esecuzione del servizio;

- un 'rimpacchettamento' del risultato, nelle locazione di memoria dell'area privata del servitore;

- una send asincrona.



PRODUTTORI CONSUMATORI

NEL MODELLO A SCAMBIO DEI MESSAGGI


Risolveremo questo problema mediante i costrutti send asincrona e receive bloccante.

In questo caso, abbiamo bisogno di un PROCESSO BUFFER, dedicato ad assolvere quelle funzioni che nel modello a memoria comune venivano svolte dal buffer di memoria condiviso tra produttori e consumatori. Ciò è necessario, perché nel modello a memoria locale non è più possibile parlare di risorse condivise (per lo meno non nell'accezione a noi nota) e quindi non si può fare ricorso ad un buffer di memoria condiviso. Si deve allora prendere un buffer di memoria e renderlo 'privato' ad un processo, che rappresenterà il gestore di tale risorsa; produttori & consumatori allora 'dialogheranno' con tale gestore per poter effettuare lo scambio dei messaggi. Lo schema di riferimento è quello riportato nella pagina seguente.


Le primitive usate sono:

SEND (mes, proc)

dove mes = variabile 'messaggio inviato', appartenente ad un certo tipo che indichiamo con T, e proc = nome del processo destinatario, appartenete al tipo che conosciamo col nome di process;

Proc : = RECEIVE (mes)

Mes sarà naturalmente una variabile dello stesso tipo. Si noti inoltre che la primitiva RECEIVE restituisce come parametro il nome del processo mittente e questo nome viene assegnato alla variabile proc (informazione a beneficio del destinatario).

Si suppone che ogni processo in grado di ricevere dei messaggi possegga una coda di messaggi a lui inviati che viene gestita dal kernel (queste code non vanno confuse con le code locali del processo buffer, di cui poi si dirà). La SEND, ogni volta che viene chiamata, inserisce il messaggio mes nella coda di ingresso del processo proc e termina. RECEIVE analizza la coda di ingresso del processo che la esegue: se è vuota, il processo viene bloccato, altrimenti viene estratto il primo messaggio e il suo valore viene assegnato alla variabile mes.




produttore 1 consumatore 1


PRONTO

  DATI


  PROCESSO

produttore i  consumatore i

DATI



DATI

  BUFFER


produttore n consumatore n




Riportiamo di seguito il codice: innanzitutto, il testo del processo buffer (spiegazioni dettagliate saranno fornite subito dopo).


Process buffer ;

var coda_dati: < coda di elementi di tipo T > ;

coda_consumatori _pronti: < coda di nomi di processo > ;

inf: T ;

consumatore, proc: process;

in: in_mess;

out: out_mess;

begin

while true do

begin

proc : = RECEIVE (in) ;

case in.specie of

dati

begin

if < coda_consumatori_pronti vuota > then

< inserzione di in.informazione nella coda_dati > ;

else

begin

< estrazione di consumatore da coda_consumatori_pronti > ;

out : = in.informazione ;

SEND (out, consumatore) ;

end ;

end ;

pronto:

begin

if < coda_dati vuota > then

< inserzione proc in coda_consumatori_pronti > ;

else

begin

< estrazione di inf da coda_dati > ;

out : = inf ;

SEND (out, proc) ;

end ;

end ;

end ;

end ;



Sono stati usati in particolare i seguenti tre tipi:


type tipo_messaggio_ricevuto = (dati, pronto) ;

type in_mess = record

case specie: tipo_messaggio ricevuto of

dati: (informazione: T) ;

pronto: () ;

end ;

type out_mess = T ;



Il testo dei processi produttori e consumatori è quello che segue:


Process produttore_i ;

var mess: in_mess ;

contenuto : T ;

begin

mess.specie : = dati ;

while true do

begin

< produci contenuto > ;

mess.informazione : = contenuto ;

SEND (mess, buffer) ;

end ;

end ;


Process consumatore_j ;

var mess1: in_mess ;

mess2: out_mess ;

proc: process ;

begin

mess1.specie : = pronto ;

while true do

begin

SEND (mess1, buffer) ;

proc : = RECEIVE (mess2) ;

< consuma mess2 > ;

end ;

end ;



Ogni consumatore notifica a buffer la sua disponibilità a ricevere dati inviandogli il messaggio di controllo pronto. Ogni produttore invia viceversa a buffer dei dati. Buffer dal canto suo svolge un lavoro di interfacciamento, inviando ai consumatori i dati in arrivo dai produttori. (cfr. schema pag.115).

I messaggi ricevuti da buffer, inviati sia dai produttori che dai consumatori, appartengono al tipo in_mess. Per discriminare fra i due casi, si usa il campo specie il quale indica se il messaggio appena ricevuto è stato spedito da un produttore (in.specie = dati) o da un consumatore (in.specie = pronto). Nel primo caso potrà essere utilizzato un secondo campo, informazione (si tratta del ben noto costrutto 'record con varianti').

I messaggi inviati da buffer ai consumatori appartengono al tipo out_mess.

Si noti che il processo BUFFER è un servitore (cfr. pag.107) e come tale rimane sospeso (sull'istruzione proc := Receive (in)) finché nella coda kernel dei messaggi in arrivo al buffer (la quale contiene sia dati che messaggi di pronta ricezione) non c'è almeno un elemento. Subito dopo la chiamata, la variabile proc conterrà l'informazione 'nome del processo chiamante'. La successiva elaborazione viene fatta come segue sulla base della natura del messaggio pervenuto.

Se il messaggio proviene da un produttore (e quindi si tratta di dati), si provvede all'invio solo se ci è almeno un consumatore in attesa. Diversamente, esso viene memorizzato da buffer in una coda locale, CODA_DATI, in cui vengono inseriti tutti i dati ricevuti dai produttori e non ancora inviati ai consumatori. Se il messaggio proviene da un consumatore (e quindi è un segnale pronto), si controlla la coda locale, e se questa non è vuota si preleva un messaggio e si procede al suo invio. Se la coda è vuota il nome del consumatore viene memorizzato in una coda locale, CODA_CONSUMATORI PRONTI, contenente i nomi dei consumatori in attesa di dati [2].

Questa soluzione presenta un evidente problema di spazio di memoria. La coda del kernel assegnata al processo buffer potrebbe saturarsi rapidamente; infatti i processi potrebbero effettuare delle send ad alta velocità in un momento in cui il buffer non è in condizione di ricevere i loro messaggi (perché non gli viene assegnata la CPU). Analogamente le code locali di buffer possono facilmente saturarsi, se i produttori inviano molti messaggi di dati che i consumatori tardano a rilevare, o se i consumatori inviano molti più messaggi di pronta ricezione di quanti non siano i messaggi di dati prodotti.


Uno dei modi di superare questa difficoltà consiste nell'usare le primitive sincrone. La loro sintassi è la stessa del caso precedente.


SEND (mess, proc);


proc : = RECEIVE (mess) ;


Questo è lo schema relativo alla soluzione che viene presentata a seguire. Esso fa riferimento al semplice caso di un solo produttore e un solo consumatore.



"richiesta di servizio" "richiesta di servizio"

Pronto Pronto

Produttore Buffer Consumatore

'cliente' 'servitore' 'cliente'

dati dati

"servizio" "servizio"



Il codice:


Process buffer ;

var coda: < coda di elementi di tipo T > ;

proc: process;

cons_pronto, prod_pronto: boolean initial false ;

dati: T ;

pronto: signal ;

begin

while true do

begin

proc : = RECEIVE (pronto) ;

case proc of

produttore :

if < coda piena > then

prod_pronto : = true ;

else

begin

proc : = RECEIVE (dati) ;

if cons_pronto then

begin

SEND (dati, consumatore) ;

cons_pronto : = false ;

end ;

else < inserzione dati in coda > ;

end ;

consumatore :

if < coda vuota > then

cons_pronto : = true ;

else

begin

< estrazione dati da coda > ;

SEND (dati, consumatore) ;

if prod_pronto then /* si è liberato un posto nella coda */

begin

proc : = RECEIVE (dati) ;

< inserzione dati in coda > ;

prod_pronto : = false ;

end :

end ;

end ;

end ;


I processi produttore e consumatore aderiscono al seguente schema:


Process produttore ;

var dati: T ;

pronto: signal ;

begin

while true do

begin

< produci dati > ;

SEND (pronto, buffer) ;

SEND (dati, buffer) ;

end ;

end ;


process consumatore ;

var dati: T ;

pronto: signal ;

proc: process;

begin

while true do

begin

SEND (pronto, buffer) ;

proc : = RECEIVE (dati) ;

< consuma dati > ;

end ;

end ;


Tutte le SEND utilizzate sono sincrone e quindi bloccano il processo che le esegue fino alla corrispondente esecuzione di una RECEIVE del processo destinatario. Si osservi che questa soluzione presenta una notevole omogeneità; grazie alla sincronizzazione implicita nella SEND, il protocollo eseguito dal produttore è equivalente a quello eseguito dal consumatore dando luogo così ad un comportamento complessivo simmetrico: entrambi i processi coinvolti sono clienti che chiedono un servizio a buffer (cfr. figura pag. 119).

Prod_pronto e cons_pronto hanno lo scopo di memorizzare la ricezione di un segnale 'pronto' da parte del buffer quando questo si trova in uno stato in cui non può soddisfare istantaneamente una richiesta. Poiché abbiamo a che fare con due soli processi, uno che produce, l'altro che consuma, sono sufficienti queste due variabili booleane; contrariamente, avremmo dovuto usare delle strutture dati più complesse.

Per poter inviare dei dati che ha generato, il produttore deve realizzare in sequenza queste operazioni:

1) invio di un segnale di disponibilità, pronto, a spedire dei dati;

2) attesa del segnale di 'ok to send' da parte di buffer; tale segnale viene ritardato nel caso in cui la coda a disposizione risulti piena: bisogna aspettare che il consumatore liberi uno degli 'scomparti' della coda [3];

3) invio vero e proprio dei dati (si noti che questa send non comporta un ritardo se non la normale attesa implicita nella send sincrona, e dunque è possibile che il produttore spedisca a più riprese dei dati fino a saturare la coda). Se c'è il consumatore ad attendere, i dati vengono subito consumati, altrimenti vengono posti nella coda.

Dal canto suo, il consumatore si limita a spedire il segnale pronto di disponibilità a ricevere dei dati, subendo un ritardo nel caso in cui la coda dei dati sia vuota. Non appena la richiesta viene soddisfatta, occorre controllare se il produttore era stato sospeso in fase di invio dati (perché aveva trovato la coda piena). In caso affermativo, i suoi dati possono essere sistemati nella coda, poiché questa ha ora un posto libero.




Contrariamente a quanto avviene per buffer, non sembrerebbe necessario provvedere al generico processo consumatore una coda, gestita dal kernel, destinata a contenere i messaggi in arrivo. L'istruzione Send (out, consumatore) del processo buffer non avrebbe motivo di inserire il messaggio in una coda di ingressi, dato che il processo consumatore non chiede mai più di un messaggio per volta (ogni send è seguita da una receive) e non è previsto che vengano inviati messaggi dei quali non si è fatta richiesta; sennonché (Ancilotti-Boari, pag.187): "La primitiva SEND asincrona prevede già, a livello di supporto a tempo di esecuzione, l'uso di code di messaggi associate ai processi". Analogamente, l'istruzione Receive (mess2)  si limita ad accettare il messaggio che gli viene passato da buffer (il quale a sua volta si serve di una coda locale, coda_dati, unica per tutti i processi) e quindi non ci sarebbe bisogno che tale istruzione prelevasse un elemento dalla testa di una coda, ma...


Pare dunque che in totale servano tre code: una, gestita dal kernel, cui fa riferimento la RECEIVE del processo buffer, e le due locali del processo buffer. Negli appunti di De Carlini in mio possesso viene sostenuto invece che le code necessarie in totale siano due: 'una all'interno del kernel per gestire le send asincrone dei produttori  e una all'interno del processo buffer per gestire i messaggi quando non ci sono consumatori pronti'. Questa affermazione non sembra plausibile.

Si noti che, proprio a motivo della presenza di un solo produttore, non è possibile che il ramo then dell'istruzione if < coda_piena >... sia percorso due volte di seguito, ossia che si esegua la prod_pronto := true una seconda volta senza che la medesima variabile sia stata prima posta a false. Similmente dicasi per cons_pronto.




Privacy




Articolo informazione


Hits: 2084
Apprezzato: scheda appunto

Commentare questo articolo:

Non sei registrato
Devi essere registrato per commentare

ISCRIVITI



Copiare il codice

nella pagina web del tuo sito.


Copyright InfTub.com 2024