|
Appunti informatica |
|
Visite: 2055 | Gradito: | [ Medio appunti ] |
Leggi anche appunti:Il futuro - DVDIl futuro - DVD Il DVD rappresenta probabilmente l'unica vera novità nel L'inventore del microprocessoreL'inventore del microprocessore Sono passati quasi trent'anni dalla sua straordinaria Generalità sul simulatore ASIMGeneralità sul simulatore ASIM ASIM (Ambiente SImulazione Microprocessori) è |
Modello a Scambio di Messaggi: Interazioni tra processi
Nel modello a scambio di messaggi ciascun processo possiede una propria memoria locale (e un proprio ambiente) non condivisa con altri processi: non c'è più competizione perchè ciascun processo tiene allocate proprie risorse private di cui è unico gestore (in questo modello il gestore di risorsa è sempre un processo), contemporaneamente l'interazione tra processi non avviene più attraverso la memoria comune (che non c'è) ma attraverso lo scambio di messaggi tra processi a livello del kernel del SO. Chiaramente una risorsa condivisa in questo modello è tale solo al livello logico (non al livello fisico, per quanto detto finora) dacchè il suo processo gestore si occupa di compiere conto terzi su di essa le operazioni richieste da processi richiedenti o client e di comunicare loro i risultati a mo' di processo servitore o server: il gestore ha quindi un ruolo attivo (anzichè passivo, come nel caso in cui fosse una risorsa gestore) nel coordinamento degli accessi alla risorsa e risolve a monte la competizione; peraltro è possibile scindere la politica di scelta dei servizi da eseguire e la loro esecuzione in distinti contesti, in modo da avere un processo gestore della risorsa e un diverso processo operante sulla risorsa. Si noti che questo modello, intuitivamente, ben si adatta a sistemi distribuiti in cui i singoli elaboratori hanno proprio memorie fisicamente separate tra loro, ma viene usato spesso anche a livello di singoli elaboratori.
Primitive Send e Receive
Lo scambio di messaggi (ovvero la comunicazione tra processi) vero e proprio avviene con l'invocazione di due classi di primitive cosiddette di send e receive, anche se al livello del kernel esistono comunque solo una singola send ed una singola receive, utilizzate poi come mattoni elementari per l'implementazione delle send e receive più sofisticate messe a disposizione dai linguaggi di programmazione concorrenti: un processo che vuole inviare un messaggio ad un altro processo (o che dualmente vuole riceverne uno) in ultima analisi invoca primitive del kernel, chiedendo quindi al SO di fare da tramite nella comunicazione tra i processi.
Le routine send e receive hanno sempre almeno due parametri: il messaggio da trasmettere/ricevere e l'identificativo del processo destinazione/mittente. Al livello del kernel le routine send e receive individuano i processi destinazione/mittente esclusivamente attraverso indirizzi di memoria, appartenenti allo spazio di indirizzamento e quindi all'area di memoria privati del processo mittente, del destinatario o del kernel. Al livello di linguaggio invece esistono tre distinte strategie: la comunicazione diretta simmetrica, con cui destinazione e mittente sono indicati esplicitamente attraverso il rispettivo PID (in particolare per la receive questo significa che il mittente del messaggio che si attende è noto PRIMA che il messaggio arrivi effettivamente); la comunicazione diretta asimmetrica, variante della precedente in cui la receive riceve il PID del mittente insieme al messaggio (lo restituisce quindi come parametro di uscita); la comunicazione indiretta, in cui send e receive fanno ambedue riferimento ad una mailbox in cui la prima deposita i messaggi e la seconda li preleva.
Si tenga presente che, per gestire l'eventualità che più processi mittente possano inviare messaggi ad uno stesso processo destinatario, il SO può predisporre all'interno del kernel una opportuna area di memoria specifica per ogni processo e gestita come una coda, in cui i messaggi ricevuti dal singolo processo vengono accodati e da cui possono essere da questo prelevati nello stesso ordine: questo tipo di struttura è, nei fatti, in tutto e per tutto assimilabile ad una mailbox privata del singolo processo in area kernel.
Una comunicazione si dice comunque completa solo quando il mittente effettua la send e il destinatario effettua la receive, e una variabile fondamentale di tale meccanismo è la modalità di sincronizzazione adottata tra i processi comunicanti, legata nei fatti alla specifica semantica delle varie primitive di send e receive invocabili: si distinguono (tra send e receive, e tra livello kernel e livello di linguaggio di programmazione) almeno 7 tipi di primitive differenti, che è possibile analizzare singolarmente nel dettaglio
1) La Send Asincrona (tipicamente il solo tipo messo a disposizione dal kernel del SO) consiste in una SVC, nella quale si specifica l'indirizzo del buffer di memoria contenente il messaggio da inviare e il PID del processo destinatario: il kernel accede al buffer indicato dal processo mittente, ne copia il contenuto in un suo apposito buffer interno al kernel stesso, restituisce il controllo al processo mittente, quest'ultimo verifica lo svuotamento del proprio buffer, assume quindi che l'operazione di invio sia stata eseguita e ritorna Running sul suo processore virtuale, proseguendo la sua esecuzione senza aspettare che il processo destinatario riceva effettivamente il messaggio. I vantaggi di questa primitiva sono la semplicità (è il mattone fondamentale con cui le altre send vengono implementate) e il massimo parallelismo che essa consente di ottenere (il mittente invia il messaggio e prosegue l'esecuzione senza attendere alcunché). Gli svantaggi sono d'altra parte legati all'impossibilità per il processo mittente/destinatario di associare l'invio/ricezione del messaggio ad uno stato del destinatario/mittente (l'assenza di ogni meccanismo di sincronizzazione giustifica il nome "asincrona") e alla capacità limitata di accumulo dei messaggi del buffer interno al kernel, poichè in caso di overflow il processo mittente può essere indotto a bloccarsi per carenza di buffer disponibili o semplicemente può perdere messaggi.
2) La Send Sincrona consiste ancora in una SVC, nella quale si specifica l'indirizzo del buffer di memoria contenente il messaggio da inviare e il PID del processo destinatario, ma stavolta la dinamica dell'invio cambia: se il destinatario aveva precedentemente eseguito una receive, il mittente viene sospeso per il tempo strettamente necessario a consentire al kernel di trasferire il contenuto del buffer del mittente direttamente nel buffer destinazione del ricevente, dopodichè ad operazione conclusa ambedue i processi tornano Running sul proprio processore virtuale; in caso contrario, il processo mittente rimane sospeso fino a che il destinatario non invoca una receive e a trasferimento dati compiuto i processi torneranno ambedue nuovamente Ready in contemporanea. I vantaggi della send sincrona corrispondono agli svantaggi di quella asincrona: prima di tutto i processi coinvolti conoscono implicitamente ciascuno lo stato dell'altro perchè sia il mittente che il destinatario risultano sbloccati non appena la trasmissione/ricezione ha avuto successo, in secondo luogo il trasferimento diretto del messaggio dal buffer sorgente a quello destinazione rende non più problematica la capacità limitata del buffer interno al kernel, poichè non c'è più il rischio che il mittente possa perdere messaggi (finchè la trasmissione corrente non è completata, il mittente è sospeso e non può quindi effettuarne altre). Ovviamente, il principale svantaggio della send sincrona è la riduzione del parallelismo, dovendo il mittente rimanere sospeso fino a trasmissione completata.
La Send Bloccante altro non è che una send sincrona - ribattezzata "bloccante" esclusivamente con riferimento a sistemi distribuiti su reti locali (vedi più avanti nel documento) - il cui comportamento corrisponde perfettamente a quello di una send sincrona nel caso in cui la comunicazione avvenga nell'ambito di una stessa macchina, viceversa prevede il "blocco" del processo mittente se il destinatario si trova su una macchina diversa e stavolta l'interlocutore diretto del processo mittente è un processo kernel detto trasmettitore (incaricato di provvedere alla trasmissione delle informazioni sulla rete). Si parla di Send Bloccante Semplice (3) se il mittente rimane sospeso finchè il trasmettitore non è libero e - una volta libero - fino a che il trasferimento del messaggio dal buffer del mittente al buffer del trasmettitore non è completato, e in seguito riprende la sua esecuzione senza curarsi del se il messaggio sia stato o meno trasmesso sulla rete. Viceversa, una Send Bloccante Sincrona (4) prevede un meccanismo più complesso, in cui il mittente rimane sospeso in attesa che il messaggio sia effettivamente trasmesso sulla rete, che il kernel dell'altra macchina lo prelevi, che il processo destinatario esegua una receive, riceva effettivamente il messaggio e comunichi con il kernel della propria macchina l'avvenuta ricezione, che il kernel della macchina destinataria comunichi con il kernel della macchina mittente l'avvenuta ricezione e che infine tale conferma arrivi al processo mittente (ovviamente un meccanismo siffatto è molto più complesso da implementare e può tenere sospeso il mittente anche per lungo tempo
5) La Receive Bloccante (tipicamente il solo tipo messo a disposizione dal kernel del SO) consiste in una SVC, nella quale si specifica l'indirizzo del buffer di memoria in cui immagazzinare il messaggio da ricevere e, alternativamente, il PID del processo destinatario oppure una mailbox da cui prelevare il messaggio: il destinatario rimane sospeso finchè non è disponibile alcun messaggio proveniente dallo specifico PID o mailbox; se il mittente esegue prima una send asincrona e il destinatario esegue dopo una receive bloccante, all'atto della receive il messaggio è già disponibile nel buffer del kernel e viene subito trasferito al buffer del processo destinatario, il quale quindi non viene affatto bloccato; se il destinatario esegue prima una receive bloccante e il mittente esegue dopo una send asincrona, il destinatario rimane bloccato fino all'aesecuzione della send allorquando il messaggio passa direttamente nel buffer del processo destinatario - senza transitare nel buffer del kernel - ed ambedue i processi vengono sbloccati; se il mittente esegue prima una send sincrona e il destinatario esegue dopo una receive bloccante, il mittente rimane bloccato fino all'esecuzione della receive allorquando il messaggio viene effettivamente trasferito ed ambedue i processi di sbloccano; se il destinatario esegue prima una receive bloccante e il mittente esegue dopo una send sincrona, il destinatario rimane bloccato fino all'esecuzione della send allorquando il messaggio viene effettivamente trasferito ed ambedue i processi di sbloccano. Il principale vantaggio di questa primitiva è la passività dell'attesa del processo ricevente (rimanendo sospeso il processo non impegna la CPU in una interrogazione continua dei processi mittente o della mailbox). Di conseguenza, il suo principale svantaggio è l'impossibilità di eseguire un polling (interrogazione continua e ciclica) su più mailbox, con l'obbligo quindi di ricevere messaggi anche tra loro eterogenei in una mailbox unica (l'attesa su di essa infatti è bloccante in assenza di messaggi disponibili
6) La Receive Non Bloccante consiste ancora in una SVC, nella quale si specifica l'indirizzo del buffer di memoria in cui immagazzinare il messaggio da ricevere e, alternativamente, il PID del processo destinatario oppure una mailbox da cui prelevare il messaggio, ma stavolta anche se non c'è alcun messaggio disponbile il processo destinatario non viene bloccato: è possibile così implementare il polling di più di una mailbox (se un messaggio arrivasse alla mailbox B, non avrebbe senso che il destinatario eseguisse una receive bloccante e rimanesse sospeso a tempo indeterminato su una mailbox A vuota!), facendo in modo che il processo destinatario, dopo l'esecuzione di una prima receive non bloccante con esito eventualmente negativo, si metta in attesa su un semaforo temporale (che diventa cioè "verde" ogni tot di tempo) per ripetere ciclicamente la receive sulle mailbox di interesse, ricevendo ogni volta un parametro che indichi se i buffer interrogati sono pieni o vuoti; la receive non bloccante è analoga alla send asincrona, nel senso che ambedue non determinano la sospensione del chiamante, e di conseguenza la ricezione di un messaggio inviato con send asincrona è asincrono tanto quanto il suo invio da parte del mittente; se viceversa il mittente esegue una send sincrona, eventuali receive non bloccanti eseguite precedentemente non hanno alcun valore e il mittente si blocca fino alla prossima receive, sia essa bloccante oppure no. I vantaggi/svantaggi della receive non bloccante corrispondono agli svantaggi/vantaggi di quella bloccante: l'attesa del processo ricevente è attiva ma in compenso è possibile il polling di più mailbox, discriminando quindi i messaggi in base al loro tipo.
Per inciso, si tenga presente che una classificazione alternativa per le primitive di Send e Receive le divide in primitive bufferizzate e primitive non bufferizzate. Una Send Asincrona (Non Bloccante) è bufferizzata perchè, oltre al proprio buffer personale, richiede al kernel un buffer supplementare, in modo da poter svuotare il suo per invii successivi, viceversa una Send Sincrona (Bloccante) non è bufferizzata. Analogamente una Receive non bufferizzata (il destinatario non dispone di un buffer di ricezione) comporta la perdita del messaggio da ricevere se, all'atto della trasmissione, il destinatario non era ancora in ascolto, mentre una Receive bufferizzata può richiedere al kernel di creare una mailbox associata ad un indirizzo destinazione, da poter consultare in modo asincrono.
7) La Chiamata a Procedura Remota (o Remote Procedure Call o RPC) è l'ultimo e più generale tipo di send, spesso detta anche rendez vous esteso, in cui il processo mittente (cliente o client) non soltanto rimane in attesa che il destinatario (servitore, servente o server) riceva il messaggio ma aspetta anche un messaggio di risposta, chiaramente con tempi di attesa generalmente più lunghi rispetto ad una semplice send sincrona o bloccante: il client richiede un servizio al server, gli passa una o più richieste e ne riceve uno o più risultati. A livello di linguaggio di programmazione concorrente una RPC viene implementata attraverso il costrutto "chiamata a procedura remota", sintatticamente analoga ad una classica chiamata a sottoprogramma, con il compilatore che si fa carico di realizzare la trasparenza della chiamata nel caso più generale in cui la routine invocata "giri" su una macchina remota: i processi client e server sono generalmente posti su macchine distinte, usano differenti spazi di indirizzamento e diversi formati di rappresentazione dei dati, per questo comunicano tra loro attraverso procedure locali dette stub (mozzicone una per il client e una per il server), segmenti di programma generati dal compilatore che si occupano di mappare chiamate a procedure locali in una o più RPC di rete; lo stub dal lato client preleva i parametri della routine invocata nel client (se i parametri sono passati by reference oppure by copy/restore lo stub si occupa di prelevarli dalle corrispondenti locazioni di memoria) e li "impacchetta" in un unico messaggio spedito in rete attraverso il kernel, lo stub dal lato server preleva il messaggio dal kernel della macchina remota, "spacchetta" i parametri e li passa alla corrispondente routine del server, la quale produrrà dei risultati da restituire (sempre attraverso gli stub) al client in un unico messaggio con la stessa procedura: le operazioni di impacchettamento, spacchettamento ed eventuale conversione dei dati eseguite dagli stub vanno sotto il nome di marshalling e garantiscono l'interoperabilità tra i processi remoti durante una RPC.
Ricapitolando, abbiamo che con la Send Asincrona il processo mittente attende solo che il messaggio venga prelevato, con la Send Bloccante il processo mittente attende la trasmisione del messaggio sulla rete, con la Send Sincrona il processo mittente attende la consegna del messaggio al destinatario, con la Receive Bloccante il processo destinatario si blocca se il messaggio non è disponibile, con la Receive Non Bloccante il processo destinatario riceve una notifica di messaggio presente/assente e prosegue la sua esecuzione. Di seguito una possibile implementazione in pascal-like delle primitive illustrate (invio e ricezione tramite mailbox)
type mailbox : record Definizione del tipo "mailbox"
primo : coda;
count : integer;
end;
var buffers : mailbox; Contiene buffers liberi (inizialmente è piena, con count>0)
buca : mailbox; Se count<0 allora |count| è il numero di processi in attesa
contenuti nella coda, se count>0 allora count è il numero di
buffers contenenti messaggi in attesa di ricezione
(inizialmente è vuota)
messaggio : T;
flag : boolean;
procedure send_asincrona(var buca, messaggio, var flag);
begin
flag:=true;
if buca.count<0 then begin
<preleva dalla coda "buca" il primo processo Proc in attesa di ricevere un messaggio>
<copia "messaggio" direttamente nel buffer di ricezione di Proc>
<metti Proc nella coda dei processi Ready>
end;
else if buffers.count>0 then begin
<preleva dalla coda "buffers" il primo buffer Buf libero>
<copia "messaggio" nel buffer Buf>
<metti Buf nella coda "buca">
end;
else flag:=false;
end;
procedure send_sincrona(var buca, messaggio);
begin
if buca.count<0 then begin
<preleva dalla coda "buca" il primo processo Proc in attesa di ricevere un messaggio>
<copia "messaggio" direttamente nel buffer di ricezione di Proc>
<metti Proc nella coda dei processi Ready>
end;
else begin
<metti il processo chiamante in attesa DI TRASMETTERE nella coda "buca">
(la logica di annessione alla coda, in questo caso, prevede di far incrementare "count" e non
di farlo decrementare, per cui si potrebbe ad esempio linkare un buffer contenente il PID del
chiamante piuttosto che il suo messaggio)
Context_Switch;
end;
end;
procedure receive_bloccante(var buca, var messaggio);
begin
if buca.count>0 then begin
<preleva dalla coda "buca" il primo buffer Buf pieno>
<copia il contenuto di Buf all'interno di "messaggio">
<metti Buf nella coda "buffers" dei buffer liberi>
end;
else begin
<metti il processo chiamante in attesa nella coda "buca">
Context_Switch;
end;
end;
procedure receive_non_bloccante(var buca, var messaggio, var flag);
begin
if buca.count>0 then begin
flag:=true;
<preleva dalla coda "buca" il primo buffer Buf pieno>
<copia il contenuto di Buf all'interno di "messaggio">
<metti Buf nella coda "buffers" dei buffer liberi>
end;
else flag:=false;
end;
procedure receive_bloccante_sincrona(var buca, var messaggio);
begin
if buca.count>0 then begin
<preleva dalla coda "buca" il primo processo Proc in attesa DI TRASMETTERE>
<copia il messaggio di Proc dentro "messaggio">
<metti Proc nella coda dei processi Ready>
end;
else begin
<metti il processo chiamante in attesa nella coda "buca">
Context_Switch;
end;
end;
procedure receive_non_bloccante_sincrona(var buca, var messaggio, var flag);
begin
if buca.count>0 then begin
flag:=true;
<preleva dalla coda "buca" il primo processo Proc in attesa DI TRASMETTERE>
<copia il messaggio di Proc dentro "messaggio">
<metti Proc nella coda dei processi Ready>
end;
else flag:=false;
end;
Si noti che la Receive Bloccante e la sua corrispondente per comunicazione con Send Sincrona possono essere unificate per gestire con la stessa routine contemporaneamente sia i messaggi inviati in modo asincrono e sia i processi bloccati in attesa di trasmettere (idem per la Receive Non Bloccante). Inoltre, poichè tipicamente le uniche due primitive messe a disposizione dal SO sono la Send Asincrona e la Receive Bloccante, è possibile servirsi di esse per implementare la Send Sincrona e la corrispondente Receive Bloccante per comunicazione con Send Sincrona
var buca : mailbox; Buca di messaggi
bucaV, bucaP : mailbox; mailbox dedicate alla sincronizzazione
procedure send_sincrona(var buca, messaggio);
var messP : T;
begin
send_asincrona(bucaV, "voglio inviare"); 2
receive_bloccante(bucaP, messP); 3
send_asincrona(buca, messaggio); 6
end;
procedure receive_bloccante_sincrona(var buca, var messaggio);
var messV : T;
begin
receive_bloccante(bucaV, messV); 1
send_asincrona(bucaP, "puoi inviare"); 4
receive_bloccante(buca, messaggio); 5
end;
dove una possibile sequenza concorrente di istruzioni è indicata dai numeretti a lato, coerentemente col fatto che dopo una receive bloccante in linea di massima sussiste un cambio di contesto; lo scopo è fare sì che quando il mittente trasmette il messaggio il ricevente sia già in ascolto, in modo che questo possa venire prelevato dal kernel e trasferito direttamente nel buffer del processo destinatario portando - a trasferimento completato - contemporaneamente ambedue i processi nello stato Ready: a tal fine, la trasmissione del messaggio vero e proprio è preceduta da una comunicazione di preparazione in cui il mittente chiede di poter trasmettere, il ricevente gli dà l'ok e si mette in attesa, il mittente riceve l'ok ed esegue finalmente la trasmissione.
Esempio: Problema dei produttori-consumatori nel modello a Scambio di Messaggi
In questo modello di memoria, produttori e consumatori non possono gestire in gruppo una risorsa gestore (dei buffer di scambio tra produttori e consumatori) condivisa e allocata staticamente nella memoria comune, in quanto la memoria comune non è disponibile, perciò è necessario definire un processo gestore nella cui memoria privata siano allocati i buffers di scambio e con il quale processi produttori e processi consumatori possano dialogare, i primi inviandogli messaggi, i secondi ricevendone se presenti
type tipo_mess_ricevuto = (dato, pronto);
type input : record
case specie: tipo_mess_ricevuto of dato: (dato : T); "Record con varianti"
pronto: ();
end;
Process buffer;
var dati : <coda di elementi di tipo T>;
dato : T;
consumatori : <coda di PID consumatori pronti>;
consumatore : process;
Proc : process;
in : input;
begin
while true do begin
Proc:=RECEIVE(in);
case in.specie of dato: begin
if <"consumatori" è vuota> then <inserisci "in.dato" in "dati">
else begin
<estrai il primo processo in coda a "consumatori" in "consumatore">
SEND(consumatore, in.dato);
end;
end;
pronto: begin
if <"dati" è vuota> then <inserisci "Proc" in "consumatori">
else begin
<estrai il primo dato in coda a "dati" in "dato">
SEND(Proc, dato);
end;
end;
end;
end;
Process produttore_i_esimo;
var messaggio : input;
begin
messaggio.specie:=dato;
while true do begin
<produzione del contenuto "messaggio.dato">
SEND(buffer, messaggio);
end;
end;
Process consumatore_i_esimo;
var messaggio : input;
Proc : process;
begin
messaggio.specie:=pronto;
while true do begin
SEND(buffer, messaggio);
Proc:=RECEIVE(messaggio.dato);
<consumo del contenuto "messaggio.dato">
end;
end;
Il meccanismo di gestione è facile da capire ogni produttore crea ciclicamente un contenuto e lo manda al processo buffer, ogni consumatore invia ciclicamente una richiesta al processo buffer attraverso un messaggio di controllo con cui manifesta la sua disponibilità a ricevere un contenuto e poi attende che gli venga effettivamente spedito, buffer infine svolge una funzione di interfacciamento accettando i contenuti dei produttori e rigirandoli ai consumatori: essendo un processo servitore, esso cicla perennemente sulla sua personale coda di messaggi in ingresso finchè almeno un messaggio è disponibile; stiamo infatti supponendo che ad ogni processo il SO assegni in area kernel una specifica e personale coda nella quale i messaggi ricevuti dal processo vengono inseriti. In realtà questo tipo di implementazione presenta l'evidente problema legato alla limitatezza delle code del processo buffer (sia la coda di messaggi in ingresso in area kernel, sia le code locali del processo) che può portare ad una loro rapida saturazione con conseguente perdita di messaggi e contenuti: può accadere infatti sia che i consumatori prelevino dal buffer i contenuti ad un ritmo inferiore a quello con cui i produttori ve li depositano, ma anche che il buffer sia troppo lento nel gestire i messaggi in arrivo, magari perchè il tempo di CPU che gli viene concesso non è sufficiente a smaltire i picchi di frequenza con cui i messaggi arrivano alla sua coda kernel.
Process buffer;
var dati : <coda di elementi di tipo T>;
dato : T;
sospeso_p, sospeso_c : boolean initial(false); "produttore/consumatore sospeso"
consumatore : process;
Proc : process;
pronto : signal;
begin
while true do begin
Proc:=RECEIVE(pronto);
case Proc of produttore: begin
if <"dati" è piena AND NOT sospeso_c> then sospeso_p:=true;
else begin
Proc:=RECEIVE(dato);
if sospeso_c then begin
SEND(consumatore, dato);
sospeso_c:=false;
end; else <inserisci "dato" nella coda "dati">
end;
end;
consumatore: begin
if <"dati" è vuota AND NOT sospeso_p> then begin
sospeso_c:=true;
consumatore:=Proc;
end;
else begin
if sospeso_p then begin
Proc:=RECEIVE(dato);
sospeso_p:=false;
end; else <estrai "dato" dalla coda "dati">
SEND(consumatore, dato);
end;
end;
end;
end;
Process produttore;
var dato : T;
pronto : signal;
begin
while true do begin
<produzione del contenuto "dato">
SEND(buffer, pronto);
SEND(buffer, dato);
end;
end;
Process consumatore;
var dato : T;
pronto : signal;
Proc : process;
begin
while true do begin
SEND(buffer, pronto);
Proc:=RECEIVE(dato);
<produzione del contenuto "dato">
end;
end;
Una implementazione alternativa e molto omogenea (il protocollo eseguito dal produttore è totalmente simmetrico a quello del consumatore) è presentata qui sopra, nel caso semplice in cui vi sia un solo produttore e un solo consumatore, e risolve il problema della saturazione delle code facendo uso di primitive sincrone: produttori e consumatori mandano un segnale di "pronto" al processo buffer prima di inviare o prelevare i messaggi e, poichè le Send e le Receive sono tutte sincrone, laddove il processo buffer non sia capace di servire istantaneamente i processi clienti (due variabili booleane memorizzano questa condizione se produttori e consumatori fossero più numerosi ne occorrerebbe un array) questi ultimi rimangono sospesi, evitando di saturare le code con invii a ripetizione che il processo buffer non sarebbe comunque in grado di gestire.
Appunti su: https:wwwappuntimaniacominformaticacomputermodello-a-scambio-di-messaggi-13php, |
|