Alcuni strumenti per lo sviluppo di software su architetture MIMD Calcolatori MIMD Architetture SM (Shared Memory) OpenMP Architetture DM (Distributed Memory) MPI 2 1
Message Passing Interface MPI www.mcs.anl.gov./research/projects/mpi/ https://computing.llnl.gov/tutorials/mpi/ https://computing.llnl.gov/tutorials/mpich2/ www.mpi-forum.orgorg 3 Processore 0 Processore 1 Processore N memory memory memory CPU CPU CPU Network Sistema a memoria distribuita Ogni processore ha una propria memoria locale alla quale accede direttamente. Ogni processore può conoscere i dati in memoria di un altro processore o far conoscere i propri, attraverso il trasferimento di dati. 4 2
Definizione: message passing ogni processore può conoscere i dati nella memoria di un altro processore o far conoscere i propri, attraverso il trasferimento di dati. Message Passing : modello per la progettazione di software in ambiente di Calcolo Parallelo. 5 La necessità di rendere portabile il modello Message Passing ha condotto alla definizione e all implementazione di un ambiente standard. Message Passing Interface MPI 6 3
Message Passing Interface MPI Non è un prodotto specifico Paradigma per la progettazione di algoritmi paralleli in termini di processi concorrenti che coordinano la propria attività comunicando 1994 MPI forum università e industrie definiscono uno standard 7 Implementazioni di MPI MPICH (Argonne National Laboratory (ANL) and Mississippi State t University (MSU)) OpenMPI,.. Linguaggi ospiti:fortran,c,c++ 8 4
Dove recuperare MPI www-unix.mcs.anl.gov/mpi/ 9 www-unix.mcs.anl.gov/mpi/ 10 5
Struttura della libreria MPI 11 Funzionalità della libreria MPI Funzioni di management della comunicazione Definizione/ identificazione di gruppi di processi coinvolti nella comunicazione i Definizione /gestione dell identità del singolo processo all interno del gruppo Funzioni di scambio di messaggi Inviare/ ricevere dati da un processo Inviare/ ricevere dati da un gruppo di processi Definizione di costanti e valori di default 12 6
Unità fondamentale è il processo Ogni processo esegue un proprio algoritmo utilizzando i dati residenti nella propria memoria quando è necessario comunica con gli altri processi 13 int main() { sum=0; for i= 0 to 14 do sum=sum+a[i]; sum+a[i]; endfor return 0; In ambiente MPI un programma è visto come un insieme di componenti (o processi) concorrenti main() { sum=0; for i=0 to 4 do sum=sum+a[i]; endfor return 0; Componente 1 main() { Componente 2 sum=0; for i=5 to 9 do sum=sum+a[i]; endfor return 0; main() { Componente 3 sum=0; for i=10 to 14do sum=sum+a[i]; endfor return 0; 14 7
15 In MPI i processori sono gestiti da due oggetti.: Gruppo Communicator 16 8
Alcuni concetti di base: GRUPPO Componente 1 Componente 2 Componente n P 0 P 1 P 0 P 0 P 1 P 2 Ogni componente concorrente del programma viene affidata ad un gruppo di processori In ogni gruppo ad ogni processore re è associato un identificativo (rank rank) L identificativo è un intero, compreso tra 0 ed il numero totale di processori appartenenti ad un gruppo decrementato di una unità Processori appartenenti a uno o più gruppi possono avere identificativi diversi, ciascuno relativo ad uno specifico gruppo 17 Communicator Communicator oggetto contenente un gruppo di processi ed un insieme di attributi associati due processi possono comunicare solo se fanno parte dello stesso communicator all interno di un communicator ogni processo ha un identificativo unico (intero da 0 a n-1) in un programma MPI può essere definito più di un communicator un processo può appartenere a più communicator 18 9
Componente 1 Componente 2 Componente n P 0 P 1 P 0 P 0 P 1 P 2 Communicator_1 Communicator_2 Communicator_m La maggior parte delle routine MPI richiedono come input un communicator 19 Tutti i processori fanno parte per default di un unico communicator detto MPI_COMM_WORLD 20 10
21 Le funzioni di MPI MPI è una libreria che comprende: Funzioni per definire l ambiente Funzioni per comunicazioni uno a uno Funzioni percomunicazioni collettive Funzioni peroperazioni collettive 22 11
Tutte le funzioni MPI,inclusi gli identificatori, iniziano con MPI_ seguito da una maiuscola e da minuscole Le costanti sono stringhe di lettere maiuscole e _ precedute da MPI Header file per un programma C #include mpi.h 23 MPI ha 125 funzioni le più usate: MPI_INT MPI_COMM_SIZE MPI_COMM_RANK MPI_SEND MPI_RECV MPI_FINALIZE 24 12
Tutte le routine MPI (tranne MPI_Wtime e MPI_Wtick ) sono function con valore di ritorno un indicatore di errore error = MPI_Xxxx(parametri,..) MPI_Xxxx(parametri,..) Questo indicatore di errore può per esempio assumere uno di questi valori: MPI_SUCCESS, nessun errore; MPI_ERR_ARG, argomento errato; MPI_ERR_RANK, identificavo errato. 25 Le funzioni dell ambiente. 26 13
Struttura di un programma MPI #include <stdio.h> #include mpi.h main(int argc, char *argv[]) { MPI_Init(&argc,&argv); :: corpo del programma MPI_Finalize(); return 0; 27 #include mpi.h mpi.h : Header File Il file contiene alcune direttive necessarie al preprocessore per l utilizzo dell MPI 28 14
#include <stdio.h> #include mpi.h main(int argc, char *argv[]) { MPI_Init(&argc,&argv); :: corpo del programma MPI_Finalize(); return 0; 29 MPI_Init(&argc,&argv); & Inizializza l ambiente di esecuzione MPI Inizializza il communicator MPI_COMMON_WORLD I due dati di input: argc ed argv sono gli argomenti del main 30 15
In generale MPI_Init(int *argc, char ***argv); Input: argc, **argv; inizializza l ambiente di esecuzione di MPI. può essere chiamata una sola volta, prima di ogni altra routine MPI Definisce l insieme dei processori attivati (communicator) 31 #include <stdio.h> #include mpi.h main(int argc, char *argv[]) { MPI_Init(&argc,&argv); :: corpo del programma MPI_Finalize(); return 0; 32 16
MPI_ Finalize(); Questa routine determina la fine del programma MPI. Dopo questa routine non è possibile richiamare nessun altra routine di MPI. 33 Rank and Size Quanti processi sono attivi? MPI_Comm_size Io chi sono? MPI_Comm_rank 34 17
#include <stdio.h> #include mpi.h int main(int argc, argc, char *argv[]) char *argv[]) { int menum, nproc; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); MPI_Comm_size(MPI_COMM_WORLD,&nproc); printf( Sono %d di %d\n,menum,nproc); di %d\n,menum,nproc); MPI_Finalize(); _ Finalize(); return return 0; 0; Tutti i processori di MPI_COMM_WORLD stampano a video il proprio identificativo menum ed il numero di processori nproc 35 #include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int menum, nproc; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); MPI_Comm_size(MPI_COMM_WORLD,&nproc); printf( Sono %d di %d\n,menum,nproc); MPI_Finalize(); return 0; Tutti i processori di MPI_COMM_WORLD stamperanno sul video il proprio identificativo menum ed il numero di processori nproc. 36 18
MPI_Comm_rank(MPI_COMM_WORLD,&menum); COMM Questa routine permette al processore chiamante, appartenente al communicator MPI_COMM_WORLD, di memorizzare il proprio identificativo nella variabile menum. 37 MPI_Comm_rank(MPI_Comm comm, int *menum); Input: comm Output: menum Fornisce ad ogni processore del communicator comm l identificativo menum. 38 19
#include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int menum, nproc; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); MPI_Comm_size(MPI_COMM_WORLD,&nproc); printf( Sono %d di %d\n,menum,nproc); MPI_Finalize(); return 0; Tutti i processori di MPI_COMM_WORLD stamperanno sul video il proprio identificativo menum ed il numero di processori nproc. 39 MPI_Comm_size(MPI_COMM_WORLD,&nproc); COMM Questa routine permette al processore chiamante di memorizzare nella variabile nproc il numero totale dei processori concorrenti appartenenti al communicator MPI_COMM_WORLD. 40 20
MPI_Comm_size(MPI_Comm comm, int *nproc); Input: comm Output: nproc Ad ogni processore del communicator comm, restituisce in nproc, il numero totale di processori che costituiscono comm. Permette di conoscere quanti processori concorrenti possono essere utilizzati per una determinata operazione 41 L output sarà del tipo sono 0 di 4 sono 1 di 4 sono 2 di 4 sono 3 di 4 42 21
La comunicazione di un messaggio La comunicazione di un messaggio può coinvolgere due o più processori. Per comunicazioni che coinvolgono solo due processori Per comunicazioni che coinvolgono più processori Si considerano funzioni MPI per comunicazioni uno a uno Si considerano funzioni MPI per comunicazioni collettive 43 Comunicazione di un messaggio uno a uno 44 22
cos è un messaggio? un messaggio è essenzialmente un blocco di dati da trasferire tra i processi 45 Il processo source effettua una chiamata ad una funzione MPI in cui è specificato il rank del processo destination nel communicator Il processo destination deve effettuare una chiamata ad una funzione MPI per ricevere il messaggio 46 23
È rispettato l ordine di invio 47 System buffer Area destinata ai dati in transito Trasparente al programmatore Può esistere per il source, il destination o entrambi Dipende dalla libreria 48 24
Caratteristiche di un messaggio Un messaggio è definito da envelope (mittente) body (dati) Un messaggio è completato se Se le locazioni di memoria utilizzate per il trasferimento t si possono riutilizzare 49 Un dato che deve essere spedito o ricevuto attraverso un messaggio di MPI è descritto dalla seguente tripla (address, count, datatype) Indirizzo in memoria del dato Dimensione del dato Tipo del dato Un datatype di MPI è predefinito e corrisponde univocamente ad un tipo di dato del linguaggio di programmazione utilizzato. 50 25
Ogni tipo di dato di MPI corrisponde univocamente ad un tipo di dato del linguaggio C. 51 Envelope (mittente): Identificativo del processo che invia Identificativo del processo che deve ricevere Identificativo del communicator a cui appartengono Un identificativo(tag) che classifica il messaggio per distinguere messaggi diversi 52 26
Tipi di comunicazioni La spedizione o la ricezione di un messaggio da parte di un processore può essere bloccante o non bloccante Se un processore esegue una comunicazione bloccante si arresta fino a conclusione dell operazione il buffer di uscita è svuotato Se un processore esegue una comunicazione non bloccante prosegue senza preoccuparsi della conclusione dell operazione. 53 Comunicazioni bloccanti in MPI Funzione bloccante per la spedizione di un messaggio: MPI_Send Funzione bloccante per la ricezione di un messaggio: MPI_Recv 54 27
Un semplice programma con 2 processori #include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int menum, nproc; int n, tag, num; MPI_Status info; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); if(menum==0) { scanf( %d,&n); tag=10; MPI_Send(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD); else { tag=10; MPI_Recv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&info); INT MPI_Get_count(&info,MPI_INT,&num); MPI_Finalize(); return 0; 55 MPI_Send(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD); Con questa routine il processore chiamante P 0 spedisce il parametro n, di tipo MPI_INT e di dimensione 1, al processore P 1 ; i due processori appartengono entrambi al communicator MPI_COMM_WORLD. Il parametro tag individua univocamente tale spedizione. n n n P 0 P 1 56 28
In generale (comunicazione uno ad uno bloccante) : MPI_Send(void *buffer, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); Il processore che esegue questa routine spedisce i primi count elementi di buffer, di tipo datatype, al processore con identificativo dest. L identificativo tag individua univocamente il messaggio nel contesto comm. 57 MPI_Send(void *buffer, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); *buffer count indirizzo del dato da spedire numero dei dati da spedire datatype tipo dei dati da spedire dest identificativo del processore destinatario t i comm identificativo del communicator tag identificativo del messaggio 58 29
#include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int menum, nproc; int n, tag, num; MPI_Status info; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); if(menum==0) { scanf( %d,&n); tag=10; MPI_Send(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD); else { tag=10; MPI_Recv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&info); MPI_Get_count(&info,MPI_INT,&num); MPI_Finalize(); return 0; 59 MPI_Recv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&info); Con questa routine il processore chiamante P 1 riceve il parametro n, di tipo MPI_INT e di dimensione 1, dal processore P 0 ; i due processori appartengono entrambi al communicator MPI_COMM_WORLD. Il parametro tag individua univocamente tale spedizione. Il parametro info, di tipo MPI_Status, contiene informazioni sulla ricezione del messaggio. n n P 1 P 0 n 60 30
In generale (comunicazione uno ad uno bloccante) : MPI_Recv(voidvoid *buffer, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status); Il processore che esegue questa routine riceve i primi count elementi in buffer, del tipo datatype, dal processore con identificativo source. L identificativo tag individua id univocamente il messaggio in comm. status è un tipo predefinito di MPI che racchiude informazioni sulla ricezione del messaggio. 61 MPI_Recv(voidvoid *buffer, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status); *buffer indirizzo del dato in cui ricevere count numero dei dati da ricevere datatype tipo dei dati da ricevere source comm identificativo del processore da cui ricevere identificativo del communicator tag identificativo del messaggio 62 31
#include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int menum, nproc; int n, tag, num; MPI_Status info; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); if(menum==0) { scanf( %d,&n); tag=10; MPI_Send(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD); else { tag=10; MPI_Recv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&info); MPI_Get_count(&info,MPI_INT,&num); MPI_Finalize(); return 0; 63 MPI_Get_count(&info,MPI_INT,&num); num : numero di elementi ricevuti Questa routine permette al processore chiamante di conoscere il numero, num, di elementi ricevuti, di tipo MPI_INT, nella spedizione individuata da info. P 0 num num P 1 =1 =1 64 32
MPI_GET_COUNT(MPI_StatusMPI_Status *status MPI_Datatype datatype, int *count); MPI_Status in C è un tipo di dato strutturato, composto da tre campi: - identificativo del processore da cui ricevere - identificativo del messaggio - indicatore di errore Il processore che esegue questa routine, memorizza nella variabile count il numero di elementi, di tipo datatype, che riceve dal messaggio e dal processore indicati nella variabile status. 65 MPI_Recv termina quando il messaggio è stato effettivamente ricevuto MPI_Send non è detto che il messaggio è stato effettivamente consegnato due modalità sincrona :il controllo ritorna solo se recv ha iniziato la ricezione buffered : il controllo ritorna appena il dato è stato copiato in un buffer Il funzionamento di default dipende dalla libreria buffered per size piccole sincrona per size grandi 66 33
Si può forzare una delle due modalità MPI_Ssend send sincrona La sintassi è come quella di MPI_ Send 67 MPI_Bsend send buffered La sintassi è come quella di MPI_ Send Il programmatore non può assumere la presenza di un buffer di sistema Deve effettuare un operazione di buffer attach e buffer detach MPI_Buffer_attach(*buf,int size) MPI_Buffer_detach(*buf,int size) buf indirizzo del buffer da allocare/deallocare size dimensione in byte del buffer 68 34
Comunicazioni non bloccanti in MPI: modalità Immediate Funzione non bloccante per la spedizione di un messaggio: MPI_Isend Funzione non bloccante per la ricezione di un messaggio: MPI_Irecv 69 Un semplice programma con 2 processori: #include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int menum, nproc, n, tag; MPI_Request rqst; MPI_Init(&argc,&argv); Init(&argc &argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); if(menum==0) { scanf( %d,&n); tag=20; MPI_Isend(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD,&rqst); else { tag=20; MPI_Irecv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&rqst); MPI_Finalize(); return 0; 70 35
MPI_Isend(&n,1,,1,MPI_INT,1,tag,MPI_COMM_WORLD,&rqst) Il processore chiamante P 0 spedisce il parametro n, di tipo MPI_INTINT e di dimensione i 1, al processore P 1.Il parametro tag individua univocamente tale spedizione. Il parametro rqst contiene le informazioni dell intera spedizione. Il processore P 0, appena inviato il parametro n, è libero di procedere nelle successive istruzioni che non implicano accesso ai dati coinvolti nella spedizione fino a che non è terminata 71 In generale (comunicazione uno ad uno non bloccante) : MPI_Isend(void *buffer, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request); Il processore che esegue questa routine spedisce i primi count elementi di buffer, del tipo datatype, al processore con identificativo dest. L identificativo tag individua univocamente il messaggio in comm. L oggetto request crea un nesso tra la trasmissione e la ricezione del messaggio 72 36
#include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int menum, nproc, n, tag; MPI_Request rqst; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&menum); if(menum==0) { scanf( %d,&n); tag=20; MPI_Isend(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD,&rqst); else { tag=20; MPI_Irecv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&rqst); MPI_Finalize(); return 0; 73 MPI_Irecv(&n,1,,1,MPI_INT,0,tag,MPI_COMM_WORLD,&rqst) Il processore chiamante P 1 riceve il parametro n, di tipo MPI_INT e di dimensione 1, dal processore P 0. Il parametro tag individua univocamente tale ricezione. Il parametro rqst contiene le informazioni dell intera spedizione. Il processore P 1, appena ricevuto il parametro n, è libero di procedere nelle successive istruzioni che non implicano accesso ai dati coinvolti nella spedizione fino a che non è terminata 74 37
Ricezione di un messaggio (comunicazione uno ad uno) MPI_Irecv(void *buffer, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request); Il processore che esegue questa routine riceve i primi count elementi in buffer, del tipo datatype, dal processore con identificativo source. L identificativo tag individua univocamente il messaggio in comm. L oggetto request crea un nesso tra la trasmissione e la ricezione del messaggio. 75 In particolare: request Le operazioni non bloccanti utilizzano l oggetto request di un tipo predefinito it di MPI: MPI_Request. Tale oggetto collega l operazione che inizia la comunicazione in esame con l operazione che la termina. 76 38
Osservazione L oggetto request ha nelle comunicazioni, un ruolo simile a quello di status. status contiene informazioni sulla ricezione del messaggio. request contiene informazioni su tutta la fase di trasmissione o di ricezione del messaggio. 77 Osservazione: termine di un operazione non bloccante Un'operazione non bloccante è terminata quando: Nel caso della spedizione, quando il buffer è nuovamente riutilizzabile dal programma. Nel caso della ricezione quando il messaggio è stato interamente ricevuto. 78 39
Osservazione: termine di un operazione non bloccante 5 3 2 7 5 3 2 7 Vettore x da spedire Operazione di spedizione non bloccante terminata Vettore x da ricevere Operazione di ricezione non bloccante terminata 79 Utilizzando una comunicazione non bloccante come si fa a sapere se l operazione di spedizione o di ricezione è stata terminata? 80 40
MPI mette a disposizione delle funzioni per controllare tutta la fase di trasmissione di un messaggio mediante comunicazione non bloccante. 81 Controllo sullo stato di un operazione non bloccante #include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int flag,nloc; nloc=1; if(menum==0) { scanf( %d,&n); tag=20; MPI_Isend(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD,&rqst); if(menum!=0) { tag=20; MPI_Irecv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&rqst); MPI_Test(&rqst,&flag,&info); &flag &info); if(flag==1){ nloc+=n;else{ MPI_Wait(&rqst,&info); nloc+=n; printf( nloc=%d \n,nloc); 82 41
MPI_Test(&rqst,&flag,&info); Il processore chiamante P 1 verifica se la ricezione di n, individuata da rqst, è stata completata; in questo caso flag=1 e procede all incremento di nloc. Altrimenti flag=0 83 MPI_Test(MPI_RequestMPI_Request *request, int *flag, MPI_Status *status); Il processore che esegue questa routine testa lo stato della comunicazione non bloccante, identificata da request. La funzione MPI_ Test ritorna l intero flag: flag = 1, l operazione identificata da request è terminata; flag = 0, l operazione identificata da request NON è terminata; 84 42
#include <stdio.h> #include mpi.h int main(int argc, char *argv[]) { int flag,nloc; nloc=1; if(menum==0) { scanf( %d,&n); tag=20; MPI_Isend(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD,&rqst); if(menum!=0) { tag=20; MPI_Irecv(&n,1,MPI_INT,0,tag,MPI_COMM_WORLD,&rqst); MPI_Test(&rqst,&flag,&info); &flag &info); if(flag==1){ nloc+=n;else{ MPI_Wait(&rqst,&info); nloc+=n; printf( nloc=%d \n,nloc); 85 MPI_Wait(&rqst,&info); Il processore chiamante P 1 procede nelle istruzioni solo quando la ricezione di n è stata completata. 86 43
In generale : MPI_Wait(MPI_RequestMPI_Request *request, MPI_Status *status); Il processore che esegue questa routine controlla lo stato della comunicazione non bloccante, identificata da request, e si arresta solo quando l operazione in esame si è conclusa. In status si hanno informazioni sul completamento dell operazione di Wait. 87 Vantaggi delle operazioni non bloccanti Le comunicazioni di tipo non bloccante hanno DUE vantaggi: 1) Il processore non è obbligato ad aspettare in stato di attesa. if(menum==0) { scanf( %d,&n); tag=20; MPI_Isend(&n,1,MPI_INT,1,tag,MPI_COMM_WORLD,&rqst); /* P 0 può procedere nelle operazioni senza dover attendere il risultato della spedizione di n al processore P 1 */ if(menum!=0) { 88 44
2) Più comunicazioni possono avere luogo contemporaneamente sovrapponendosi almeno in parte tra loro. if(menum==0) { /* P 0 spedisce due elementi a P 1 */ MPI_Isend(&a,1,MPI_INT,1,0,MPI_COMM_WPRLD,&rqst1); MPI_Isend(&b,1,MPI_INT,1,0,MPI_COMM_WORLD,&rqst2); elseif(menum==1) { /* P 1 riceve due elementi da P 0 secondo l ordine di spedizione*/ MPI_Irecv(&a,1,MPI_INT,0,0,MPI_COMM_WORLD,&rqst1); MPI_Irecv(&b,1,MPI_INT,0,0,MPI_COMM_WORLD,&rqst2); svantaggio la programmazione è più complicata 89 45