Multithreading, multiprocessing e Asincronia

  • Published on
    10-May-2015

  • View
    842

  • Download
    2

Embed Size (px)

DESCRIPTION

A brief introduction to parallellization techniques on linux

Transcript

<ul><li>1.Linux Internals:Multiprocessing, Multithreading e AsincroniaCome aumentare le prestazioni parallelizzandoSebastiano Merlino merlino.sebastiano@gmail.com</li></ul><p>2. Perch parallellizzare? eMigliora le prestazioni del programma (mentre un processo ` in eattesa per esempio in fase di I/O, un altro pu` lavorare).oMigliora la distribuibilit` del programma (rendendolo pronto perala scalabilit` orizzontale). a` spesso vitale e necessario (come nel caso delle interfaccieEgrache).`E utile a volte persino in termini di modularit` e leggibilit` del a acodice. 3. MultiprocessingCon multiprocessing, in questo contesto, intendiamolesecuzione di multipli processi software concorrenti cheabbiano spazi di memoria indipendenti.I processi su Linux proliferano mediante meccanismo digenerazione. Il processo di sistema operativo che si occupa dellagenerazione di tutti i processi kernel necessari ` init. Ogni eprocesso ` identicato da un PID univoco.eIl meccanismo scelto su linux per la generazione di nuoviprocessi ` la cosiddetta fork utilizzando la quale un processo esdoppia il proprio spazio di indirizzi in memoria permetterndolesecuzione di un nuovo processo che sfrutti questa copia. 4. ForkLa funzione fork (int fork()) trasforma un singolo processo inesecuzione in due processi identici, noti come parent e child.Quando lesecuzione ha successo, la fork ritorna 0 al processochild e il PID del child al processo parent.#include #include int main() {int cpid = fork();if(cpid &gt; 0) {printf("I am the father and this is my child: %dn", cpid);} else if (cpid == 0) {printf("I am the childn");} else {printf("Error!");}} 5. Meccanismi di IPC`E spesso necessario rendere possibile ai processi creati ilcomunicare fra loro.I sistemi di IPC (Inter-process communication) che Linuxfornisce sono svariati:Pipe (&lt; stdio.h &gt;)Signal (&lt; signal.h &gt;)Message Queues (&lt; sys/msg .h &gt;)Semaphores (&lt; sys/sem.h &gt;)Shared Memory (&lt; sys/shm.h &gt;)Socket (&lt; sys/socket.h &gt;) 6. PipeLe Pipe sono oggetti grazie ai quali loutput di un processo pu` oessere iniettato in input ad un altro processo.Linux fornisce due modi di denire pipe: 1. FILE* popen(char* command, char* type) - apre una pipeove command ` il processo con il quale ci vogliamo connettere.eIl tipo pu` essere r in lettura o w in scrittura. Le pipe cos`oaperte vanno chiuse tramite pclose(FILE* fs) e vengonoscritte/lette tramite lutilizzo di fprintf e fscanf. 2. int pipe(int fd[2]) - crea una pipe restituendo due ledescriptor. fd[0] ` aperto in lettura ed fd[1] ` aperto ineescrittura. Le pipe vanno sempre chiuse mediante metodoclose(int fd). Vengono sfruttati i metodi read e write. 7. #include #include int main() {int pdes[2];pipe(pdes);if(fork() == 0) {char received_message[10];close(pdes[1]);read(pdes[0], (void*) received_message, 9);printf("message received: %sn", received_message);} else {close(pdes[0]);write(pdes[1], (void*) "Hello!", 7);printf("message sent: Hello!n");}return 0;} 8. SignalI segnali sono interrupt generati via software che possono essereinviati ad un processo per segnalargli che ` avvenuto un evento. eI segnali possono essere generati in maniera sincrona, ma sonogenealmente meccanismi asincroni. Essi sono identicatiunivocamente da un numero intero compreso tra 0 e 31.Due sono i modi pi` comuni per linvio di un segnale:u int kill(int pid, int signal) - ` una chiamata di sistema chee permette di emettere un segnale. Se il pid ` maggiore di zero, ile processo da esso identicato sar` lunico destinatario; nel casoa in cui il pid sia zero, il messaggio sar` inviato a tutti. a int raise(int signal) - invia un messaggio al processo running. Utilizza kill in maniera trasparente ponendo come pid quello del processo che la esegue. 9. SignalUnapplicazione pu` denire una funzione cosiddetta che verr`o ainvocata quando uno specico segnale viene ricevuto.Lhandler per i segnali viene denito tramite la chiamata int(*signal(int sig, void (*func)()))(); ricevuto il segnaleidenticato da sig, la funzione func verr` invocata. func pu` aoassumere tre valori:SIG DFL - funzione di default; il processo sar` terminato unaavolta ricevuto il segnaleSIG IGN - funzione ignore; il segnale verr` catturato ed ignoratoaun generico puntatore a funzione. 10. #include #include void sigproc(void);void quitproc(void);main(){ signal(SIGINT, sigproc); signal(SIGQUIT, quitproc); printf(ctrl-c disabled use ctrl-$backslash$$backslash$ to quit$backslash$n); for(;;); /* infinite loop */}void sigproc(){ signal(SIGINT, sigproc); /* */ /* NOTE some versions of UNIX will reset signal to default after each call. So for portability reset signal each time */ printf(you have pressed ctrl-c $backslash$n);}void quitproc(){ printf(ctrl-$backslash$$backslash$ pressed to quit$backslash$n); exit(0); /* normal exit status */} 11. Message QueuesLe code di messaggi si basano su uno schema molto semplice.Un processo piazza un messaggio allinterno di una struttura(una coda) fornita dal sistema operativo. Un altro processo,conoscendo lidenticativo della coda da cui leggere potr`asuccessivamente recuperare il messaggio.Una coda di messaggi pu` essere inizializzata mediante ilometodo int msgget(key t key, int msgg); il metodo ritornalidenticativo univoco della coda - i parametri specicatirappresentano il nome assengato alla coda e dei ag utili per ilmanagement dei diritti.Un messaggio viene inserito nella coda tramite il metodomsgsnd e viene invece recuperato mediante msgrcv. 12. SemaphoresI semafori costituiscono un fondamentale sistema di coordinamentotra processi.Qualora due o pi` processi condividano la stessa risorsa mediante umeccanismi di IPC ` vitale che laccesso a tale risorsa siaecorrettamente gestito (osserveremo meglio questo problema nellaparte dedicata ai thread).Un semaforo viene inizializzato (o se ne ottiene accesso) tramite: intsemget(key t key, int nsems, int semg); ove, key rappresenta unnome associato allid univoco che la funzione ritorna; nsems indica ilnumero di semafori che si vogliono ottenere e semg sono i ag digestione assegnati al semaforo.Operazioni sul semaforo vengono eseguite mediante la direttiva intsemop(int semid, struct sembuf *sops, size t nsops); 13. Shared MemoryLa shared memory rappresenta il metodo, forse pi` veloce di scambiare dati frauprocessi dierenti. In sostanza, un processo crea unarea di memoria alla qualealtri processi possono accedere (se viene loro dato diritto). Questo metodo siporta ovviamente dietro il problema della condivisione di una risorsa e pertantoporta allinsorgenza della necessit` di sincronizzare laccesso da parte dei processi aalla memoria condivisa.Un segmento di memoria condivisa ` acceduto tramite int shmget(key t key, size t esize, int shmg);Per potere usare la memoria condivisa, il processo deve farne attach al suo spaziodi indirizzi; questo viene fatto tramite void *shmat(int shmid, const void*shmaddr, int shmg); che ritorner` in shmaddr lindirizzo alla head dellaamemoria condivisa.Il processo rilascia laccesso tramite int shmdt(const void *shmaddr);Trascuro per ora le problematiche di sincronizzazione, essendo queste simili permolti versi a quelle successivamente trattate nella parte dedicata ai thread. 14. SocketLe Socket costituiscono un sistema di IPC punto-punto e bidirezione. Purnon essendo il sistema pi` veloce di comunicazione, la loro versatilit` e la uapossibilit` di usarle attraverso la rete le rendono molto usate.aEsistono 4 tipi di socket: Stream Socket (SOCK STREAM) - bidirezionali, sequenziali, adabili e non duplicati senza limiti di trasmissione. Usa TCP. Datagram Socket (SOCK DGRAM) - bidirezionali, non assicurano lordine dei messaggi che sono costituiti da pacchetti di dimensione nita. Non necessita di connessione. Usa UDP Sequential Packet Socket (SOCK SEQPACKET) - essenzialmente un Datagram ma sequenziale, adabile e con connessione. Mai implementato. Raw - utilizza direttamente i protocolli di comunicazione. 15. SocketLe Socket vengono create mediante la funzione int socket(int domain, int type, intprotocol) (se il protocollo non ` specicato esso sar` il default per il tipo). Le Socket sieacollegano le une alle altre tramite indirizzi. Per assegnare un indirizzo ad una socket siusa il metodo int socket(int domain, int type, int protocol).Le connessioni tramite Socket sono solitamente non bilanciate (client-server). Il server(in una connessione STREAM) chiama int listen(int s, int backlog) ove backlograppresenta il numero massimo di connessioni da accodare.Il client per collegarsi al Server chiamer` int connect(int s, struct sockaddr *name, intanamelen) e, inne, il server, per accettare la connessione eseguir` int connect(int s,astruct sockaddr *name, int namelen) che restituisce il le descriptor di una socketdedicata esclusivamente alla connessione.In una socket stream, la ricezione e linvio dei messaggi sono eseguiti mediante i metodiread() e write() o, alternativamente, recv(...) e send(...).In una socket datagram, invece si usano i metodi sendto() e recvfrom() o, in alternativa,recvmsg() e sendmsg. 16. Soluzioni dierenti - OpenMPOpenMP ` una API che supporta il multiprocessing con memoria condivisa. eLa semantica della libreria ` cos` semplice da farla apparire quasi come una funzionalit` del linguaggio. e aLa libreria si occupa in maniera del tutto trasparente per il programmatore del partizionamento dei dati e delladistribuzione tra i core del codice.#include int main(void){#pragma omp parallelprintf("Hello, world.n"); // Verr` stampato una volta per ogni processoreareturn 0;}int main(int argc, char *argv[]) {const int N = 100000;int i, a[N];#pragma omp parallel for // k cicli saranno eseguiti in parallelo (con k numero di processori)for (i = 0; i &lt; N; i++)a[i] = 2 * i;return 0;} 17. MultithreadingPossiamo considerare un Thread come un lightweight Process.Lutilizzo di soluzioni multithread ha grandi beneci:Aumenta la responsivit` - in presenza di GUI, ad esempio,apermette di dedicare un thread ad ogni attivit` graca.aUsa in maniera pi` eciente i sistemi multiprocessore - ` pi`u e ufacile scalare rispetto al numero di processori applicazionimultithread.Migliora la struttura di programma - molti programmi risultanomeglio strutturati come sistemi a parti semi-indipendenti checome monoliti. 18. Confronto tra Multiprocessing eMultithreadingI beneci dei thread rispetto ai processi consistono essenzialmente in:minor tempo per essere creati (dato che sfruttano laddressspace senza copiarlo)minor tempo per essere terminatiminor tempo per switchare da un thread ad un altro (rispetto aquello richiesto per switchare da un processo allaltro)minore overhead dovuto alla comunicazioneIl vantaggio dei processi sui thread `, invece rappresentato da un emaggior livello di isolamento. 19. PthreadsLa libreria pi` importante per la realizzazione di codiceumultithread ` libpthread (pthread.h).ePer creare un thread si usa int pthread create(pthread t *tid,const pthread attr t *tattr, void*(*start routine)(void *),void *arg); - se non si passano attributi, il thread sar` nonadetachato e erediter` la priorit` del padre.a aLa libpthread ` parecchio complessa e contiene molti metodieper la gestione e linizializzazione dei thread dei quali nontratteremo qui (creare variabili thread local, gestione priorit`, aecc...). 20. SincronizzazioneLa libpthread ore anche vari meccanismi di sincronizzazione fra ithread. La sincronizzazione in questo caso ` vitale poich i thread e econdividono lo stesso spazio di indirizzi.Mutual exclusion locks (Mutex)Condition variable attributesSemaphores (non li tratteremo in quanto molto simili a quellivisti per i processi) 21. Sincronizzazione MutexI mutex rappresentano un sistema molto diuso di serializzazione delleoperazioni; essi sincronizzano i thread serializzando laccesso aporzioni del codice denite sezioni critiche.Un mutex viene creato tramite le funzione intpthread mutex init(pthread mutex t *mp, constpthread mutexattr t *mattr); - ove il passaggio degli attributi ` deletutto arbitrario.Esistono mutex di varia natura (unici, condivisi, scoped, ecc...) e, levarie tipologie, se non fornite in libreria, sono facilmente realizzabili.I mutex vengono lockati e slockati rispettivamente tramite le funzionipthread mutex lock e pthread mutex unlock. 22. SincronizzazioneCondition VariablesLe Condition Variables sono usate per bloccare atomicamente un thread nch unedeterminato evento non si verica; esse sono sempre utilizzate in congiunzione conMutex e lock. Si controlla la condizione dopo avere acquisito il lock e, nel casoquesta non sia vericata ci si mette in wait sulla condizione mentre atomicamentesi rilascia il lock. Quando un altro thread cambia la condizione e notica il cambiosi riacquisisce il lock e si rivaluta la condizione.Le Condition Variables sono inizializzate cos` int pthread cond init(pthread cond t *cv, const pthread condattr t *cattr);Varie versioni del metodo wait (semplice, temporizzata, ecc...) possono essereusate per attendere che la condizione si verichiLa condizione pu` essere segnalata mediante: intopthread cond signal(pthread cond t *cv);.Il modo corretto di mettersi in attesa su una condition variable ` il seguente: epthread_mutex_lock();while(condition_is_false) // previene Spurious WakeUppthread_cond_wait();pthread_mutex_unlock(); 23. Moderne Amenit`aNegli anni, molte altre librerie che abilitano il multithreading sononate. Fra queste ritroviamo: boost::thread - contiene tutti i maggiori paradigmi di gestione dei thread e della loro sincronizzazione Qt - dispone di versioni ad alto livello di trasparenza delle funzionalit` n qui espostea 24. AsincroniaRisulta spesso utile non assegnare specicatamente un job ad unthread ma mantenere un pool di thread detti workers ed assegnare aduno di essi un job solo quando si verichi uno specico evento che lorichieda. Questidea ` alla base dei pattern reactor e proactor.eUn sistema di questo tipo ` detto asincrono in quanto si oppone aleclassico meccanismo sincrono basato su loop e polling o wait sucondition variable in cui un job ` assegnato sempre e solo ad un ethread (nel quale quindi, per ogni job viene creato un nuovo thread).Questo sistema ` vincente in quanto ottimizza le prestazioni del eprogramma sia in termini di cicli eseguiti che in termini di memoriaoccupata; tra laltro esso rende la quantit` di risorse necessarie al aprogramma prevedibile in ogni momento e non strettamentedipendente dagli input. 25. AsincroniaUn problema molto sentito ` quello della gestione multithread di unesistema Thread che riceve richieste da vari client. Se le richiestefossero servite sequenzialmente, non sarebbe possibile gestire richiesteconcorrenti.La prima soluzione che ci viene in mente ` quella di creare un thread eper ciascuna richiesta ricevuta. In questo modo sicuramente riceviamoil problema di gestione ma si presenta un nuovo problema. Se, infatti,ricevessimo nello stesso istante una gran quantit` di richieste, eg. a10000, avremmo uno spawn istantaneo di 10000 thread che potrebbeportare al kill del nostro processo da...</p>

Recommended

View more >