multithreading, multiprocessing e asincronia

26
Linux Internals: Multiprocessing, Multithreading e Asincronia Come aumentare le prestazioni parallelizzando Sebastiano Merlino [email protected]

Upload: sebastiano-merlino

Post on 10-May-2015

860 views

Category:

Technology


2 download

DESCRIPTION

A brief introduction to parallellization techniques on linux

TRANSCRIPT

Page 1: Multithreading, multiprocessing e Asincronia

Linux Internals:Multiprocessing, Multithreading e

Asincronia

Come aumentare le prestazioni parallelizzando

Sebastiano [email protected]

Page 2: Multithreading, multiprocessing e Asincronia

Perche parallellizzare?

I Migliora le prestazioni del programma (mentre un processo e inattesa per esempio in fase di I/O, un altro puo lavorare).

I Migliora la distribuibilita del programma (rendendolo pronto perla scalabilita orizzontale).

I E spesso vitale e necessario (come nel caso delle interfacciegrafiche).

I E utile a volte persino in termini di modularita e leggibilita delcodice.

Page 3: Multithreading, multiprocessing e Asincronia

Multiprocessing

I Con multiprocessing, in questo contesto, intendiamol’esecuzione di multipli processi software concorrenti cheabbiano spazi di memoria indipendenti.

I I processi su Linux proliferano mediante meccanismo digenerazione. Il processo di sistema operativo che si occupa dellagenerazione di tutti i processi kernel necessari e init. Ogniprocesso e identificato da un PID univoco.

I Il meccanismo scelto su linux per la generazione di nuoviprocessi e la cosiddetta fork utilizzando la quale un processosdoppia il proprio spazio di indirizzi in memoria permetterndol’esecuzione di un nuovo processo che sfrutti questa copia.

Page 4: Multithreading, multiprocessing e Asincronia

Fork

I La funzione fork (int fork()) trasforma un singolo processo inesecuzione in due processi identici, noti come parent e child.

I Quando l’esecuzione ha successo, la fork ritorna 0 al processochild e il PID del child al processo parent.

#include <unistd.h>

#include <stdio.h>

int main() {

int cpid = fork();

if(cpid > 0) {

printf("I am the father and this is my child: %d\n", cpid);

} else if (cpid == 0) {

printf("I am the child\n");

} else {

printf("Error!");

}

}

Page 5: Multithreading, multiprocessing e Asincronia

Meccanismi di IPC

I E spesso necessario rendere possibile ai processi creati ilcomunicare fra loro.

I I sistemi di IPC (Inter-process communication) che Linuxfornisce sono svariati:

I Pipe (< stdio.h >)I Signal (< signal .h >)I Message Queues (< sys/msg .h >)I Semaphores (< sys/sem.h >)I Shared Memory (< sys/shm.h >)I Socket (< sys/socket.h >)

Page 6: Multithreading, multiprocessing e Asincronia

PipeLe Pipe sono oggetti grazie ai quali l’output di un processo puoessere iniettato in input ad un altro processo.Linux fornisce due modi di definire pipe:

1. FILE* popen(char* command, char* type) - apre una pipeove command e il processo con il quale ci vogliamo connettere.Il tipo puo essere “r” in lettura o “w” in scrittura. Le pipe cosıaperte vanno chiuse tramite pclose(FILE* fs) e vengonoscritte/lette tramite l’utilizzo di fprintf e fscanf.

2. int pipe(int fd[2]) - crea una pipe restituendo due filedescriptor. fd[0] e aperto in lettura ed fd[1] e aperto inscrittura. Le pipe vanno sempre chiuse mediante metodoclose(int fd). Vengono sfruttati i metodi read e write.

Page 7: Multithreading, multiprocessing e Asincronia

#include <signal.h>

#include <stdio.h>

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: %s\n", received_message);

} else {

close(pdes[0]);

write(pdes[1], (void*) "Hello!", 7);

printf("message sent: Hello!\n");

}

return 0;

}

Page 8: Multithreading, multiprocessing e Asincronia

SignalI segnali sono interrupt generati via software che possono essereinviati ad un processo per segnalargli che e avvenuto un evento.I segnali possono essere generati in maniera sincrona, ma sonogenealmente meccanismi asincroni. Essi sono identificatiunivocamente da un numero intero compreso tra 0 e 31.Due sono i modi piu comuni per l’invio di un segnale:

I int kill(int pid, int signal) - e una chiamata di sistema chepermette di emettere un segnale. Se il pid e maggiore di zero, ilprocesso da esso identificato sara l’unico destinatario; nel casoin cui il pid sia zero, il messaggio sara inviato a tutti.

I int raise(int signal) - invia un messaggio al processo running.Utilizza kill in maniera trasparente ponendo come pid quello delprocesso che la esegue.

Page 9: Multithreading, multiprocessing e Asincronia

Signal

I Un’applicazione puo definire una funzione cosiddetta che verrainvocata quando uno specifico segnale viene ricevuto.

I L’handler per i segnali viene definito tramite la chiamata int(*signal(int sig, void (*func)()))(); ricevuto il segnaleidentificato da sig, la funzione func verra invocata. func puoassumere tre valori:

I SIG DFL - funzione di default; il processo sara terminato unavolta ricevuto il segnale

I SIG IGN - funzione ignore; il segnale verra catturato ed ignoratoI un generico puntatore a funzione.

Page 10: Multithreading, multiprocessing e Asincronia

#include <stdio.h>

#include <signal.h>

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 */

}

Page 11: Multithreading, multiprocessing e Asincronia

Message Queues

I Le code di messaggi si basano su uno schema molto semplice.Un processo piazza un messaggio all’interno di una struttura(una coda) fornita dal sistema operativo. Un altro processo,conoscendo l’identificativo della coda da cui leggere potrasuccessivamente recuperare il messaggio.

I Una coda di messaggi puo essere inizializzata mediante ilmetodo int msgget(key t key, int msgflg); il metodo ritornal’identificativo univoco della coda - i parametri specificatirappresentano il nome assengato alla coda e dei flag utili per ilmanagement dei diritti.

I Un messaggio viene inserito nella coda tramite il metodomsgsnd e viene invece recuperato mediante msgrcv.

Page 12: Multithreading, multiprocessing e Asincronia

Semaphores

I I semafori costituiscono un fondamentale sistema di coordinamentotra processi.

I Qualora due o piu processi condividano la stessa risorsa mediantemeccanismi di IPC e vitale che l’accesso a tale risorsa siacorrettamente gestito (osserveremo meglio questo problema nellaparte dedicata ai thread).

I Un semaforo viene inizializzato (o se ne ottiene accesso) tramite: intsemget(key t key, int nsems, int semflg); ove, key rappresenta unnome associato all’id univoco che la funzione ritorna; nsems indica ilnumero di semafori che si vogliono ottenere e semflg sono i flag digestione assegnati al semaforo.

I Operazioni sul semaforo vengono eseguite mediante la direttiva intsemop(int semid, struct sembuf *sops, size t nsops);

Page 13: Multithreading, multiprocessing e Asincronia

Shared Memory

I La shared memory rappresenta il metodo, forse piu veloce di scambiare dati fraprocessi differenti. In sostanza, un processo crea un’area 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 all’insorgenza della necessita di sincronizzare l’accesso da parte dei processialla memoria condivisa.

I Un segmento di memoria condivisa e acceduto tramite int shmget(key t key, size tsize, int shmflg);

I 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 shmflg); che ritornera in shmaddr l’indirizzo alla head dellamemoria condivisa.

I Il processo rilascia l’accesso tramite int shmdt(const void *shmaddr);

I Trascuro per ora le problematiche di sincronizzazione, essendo queste simili permolti versi a quelle successivamente trattate nella parte dedicata ai thread.

Page 14: Multithreading, multiprocessing e Asincronia

SocketLe Socket costituiscono un sistema di IPC punto-punto e bidirezione. Purnon essendo il sistema piu veloce di comunicazione, la loro versatilita e lapossibilita di usarle attraverso la rete le rendono molto usate.Esistono 4 tipi di socket:

I Stream Socket (SOCK STREAM) - bidirezionali, sequenziali, affidabilie non duplicati senza limiti di trasmissione. Usa TCP.

I Datagram Socket (SOCK DGRAM) - bidirezionali, non assicuranol’ordine dei messaggi che sono costituiti da pacchetti di dimensionefinita. Non necessita di connessione. Usa UDP

I Sequential Packet Socket (SOCK SEQPACKET) - essenzialmente unDatagram ma sequenziale, affidabile e con connessione. Maiimplementato.

I Raw - utilizza direttamente i protocolli di comunicazione.

Page 15: Multithreading, multiprocessing e Asincronia

Socket

Le Socket vengono create mediante la funzione int socket(int domain, int type, intprotocol) (se il protocollo non e specificato esso sara il default per il tipo). Le Socket sicollegano 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 chiamera int connect(int s, struct sockaddr *name, intnamelen) e, infine, il server, per accettare la connessione eseguira int connect(int s,struct sockaddr *name, int namelen) che restituisce il file descriptor di una socketdedicata esclusivamente alla connessione.In una socket stream, la ricezione e l’invio 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.

Page 16: Multithreading, multiprocessing e Asincronia

Soluzioni differenti - OpenMPOpenMP e una API che supporta il multiprocessing con memoria condivisa.La semantica della libreria e cosı semplice da farla apparire quasi come una funzionalita del linguaggio.La libreria si occupa in maniera del tutto trasparente per il programmatore del partizionamento dei dati e delladistribuzione tra i core del codice.

#include <stdio.h>

int main(void)

{

#pragma omp parallel

printf("Hello, world.\n"); // Verra stampato una volta per ogni processore

return 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 < N; i++)

a[i] = 2 * i;

return 0;

}

Page 17: Multithreading, multiprocessing e Asincronia

Multithreading

Possiamo considerare un Thread come un lightweight Process.L’utilizzo di soluzioni multithread ha grandi benefici:

I Aumenta la responsivita - in presenza di GUI, ad esempio,permette di dedicare un thread ad ogni attivita grafica.

I Usa in maniera piu efficiente i sistemi multiprocessore - e piufacile scalare rispetto al numero di processori applicazionimultithread.

I Migliora la struttura di programma - molti programmi risultanomeglio strutturati come sistemi a parti semi-indipendenti checome monoliti.

Page 18: Multithreading, multiprocessing e Asincronia

Confronto tra Multiprocessing e

Multithreading

I benefici dei thread rispetto ai processi consistono essenzialmente in:

I minor tempo per essere creati (dato che sfruttano l’addressspace senza copiarlo)

I minor tempo per essere terminati

I minor tempo per switchare da un thread ad un altro (rispetto aquello richiesto per switchare da un processo all’altro)

I minore overhead dovuto alla comunicazione

Il vantaggio dei processi sui thread e, invece rappresentato da unmaggior livello di isolamento.

Page 19: Multithreading, multiprocessing e Asincronia

Pthreads

I La libreria piu importante per la realizzazione di codicemultithread e libpthread (pthread.h).

I Per 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 sara nondetachato e ereditera la priorita del padre.

I La libpthread e parecchio complessa e contiene molti metodiper la gestione e l’inizializzazione dei thread dei quali nontratteremo qui (creare variabili thread local, gestione priorita,ecc...).

Page 20: Multithreading, multiprocessing e Asincronia

Sincronizzazione

La libpthread offre anche vari meccanismi di sincronizzazione fra ithread. La sincronizzazione in questo caso e vitale poiche i threadcondividono lo stesso spazio di indirizzi.

I Mutual exclusion locks (Mutex)

I Condition variable attributes

I Semaphores (non li tratteremo in quanto molto simili a quellivisti per i processi)

Page 21: Multithreading, multiprocessing e Asincronia

Sincronizzazione

Mutex

I I mutex rappresentano un sistema molto diffuso di serializzazione delleoperazioni; essi sincronizzano i thread serializzando l’accesso aporzioni del codice definite sezioni critiche.

I Un mutex viene creato tramite le funzione intpthread mutex init(pthread mutex t *mp, constpthread mutexattr t *mattr); - ove il passaggio degli attributi e deltutto arbitrario.

I Esistono mutex di varia natura (unici, condivisi, scoped, ecc...) e, levarie tipologie, se non fornite in libreria, sono facilmente realizzabili.

I I mutex vengono lockati e slockati rispettivamente tramite le funzionipthread mutex lock e pthread mutex unlock.

Page 22: Multithreading, multiprocessing e Asincronia

Sincronizzazione

Condition VariablesI Le Condition Variables sono usate per bloccare atomicamente un thread finche un

determinato evento non si verifica; esse sono sempre utilizzate in congiunzione conMutex e lock. Si controlla la condizione dopo avere acquisito il lock e, nel casoquesta non sia verificata ci si mette in wait sulla condizione mentre atomicamentesi rilascia il lock. Quando un altro thread cambia la condizione e notifica il cambiosi riacquisisce il lock e si rivaluta la condizione.

I Le Condition Variables sono inizializzate cosı intpthread cond init(pthread cond t *cv, const pthread condattr t *cattr);

I Varie versioni del metodo wait (semplice, temporizzata, ecc...) possono essereusate per attendere che la condizione si verifichi

I La condizione puo essere segnalata mediante: intpthread cond signal(pthread cond t *cv);.

I Il modo corretto di mettersi in attesa su una condition variable e il seguente:

pthread_mutex_lock();

while(condition_is_false) // previene Spurious WakeUp

pthread_cond_wait();

pthread_mutex_unlock();

Page 23: Multithreading, multiprocessing e Asincronia

Moderne Amenita

Negli anni, molte altre librerie che abilitano il multithreading sononate. Fra queste ritroviamo:

I boost::thread - contiene tutti i maggiori paradigmi di gestionedei thread e della loro sincronizzazione

I Qt - dispone di versioni ad alto livello di trasparenza dellefunzionalita fin qui esposte

Page 24: Multithreading, multiprocessing e Asincronia

AsincroniaI Risulta spesso utile non assegnare specificatamente un job ad un

thread ma mantenere un pool di thread detti workers ed assegnare aduno di essi un job solo quando si verifichi uno specifico evento che lorichieda. Quest’idea e alla base dei pattern reactor e proactor.

I Un sistema di questo tipo e detto asincrono in quanto si oppone alclassico meccanismo sincrono basato su loop e polling o wait sucondition variable in cui un job e assegnato sempre e solo ad unthread (nel quale quindi, per ogni job viene creato un nuovo thread).

I Questo sistema e vincente in quanto ottimizza le prestazioni delprogramma sia in termini di cicli eseguiti che in termini di memoriaoccupata; tra l’altro esso rende la quantita di risorse necessarie alprogramma prevedibile in ogni momento e non strettamentedipendente dagli input.

Page 25: Multithreading, multiprocessing e Asincronia

AsincroniaI Un problema molto sentito e quello della gestione multithread di un

sistema Thread che riceve richieste da vari client. Se le richiestefossero servite sequenzialmente, non sarebbe possibile gestire richiesteconcorrenti.

I La prima soluzione che ci viene in mente e quella di creare un threadper 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 quantita di richieste, eg.10000, avremmo uno spawn istantaneo di 10000 thread che potrebbeportare al kill del nostro processo da parte del sistema operativo.

I La soluzione a questo problema (peraltro noto come c10k problem) sibasa proprio su meccanismi asincroni come quelli descrittiprecedentemente; molti di questi si basano sull’utilizzo di select epoll.

Page 26: Multithreading, multiprocessing e Asincronia

select e pollI select e poll sono potenti strumenti per il multiplexing delle socket. Un

programmatore puo usare questi strumenti per scoprire quando un evento discrittura sia avvenuto su una socket cosı da comprendere che e il momentodi leggere.

I La select funziona come segue: int select(int nfds, fd set *readfds, fd set*writefds, fd set *errorfds, struct timeval *timeout); ove nfds e il fd divalore piu alto, readfds, writefds e errorfds sono dei set di file descriptor dacontrollare e timeout e un eventuale timeout che scatta qualora nonavvengano eventi su nessun file descriptor. La funzione torna il numero difd su cui e avvenuto un evento e riempie i set con solo i file descriptor sucui sono avvenuti eventi.

I La poll e un’evoluzione della select che ne migliora essenzialmente lasemantica. Essa permette di specificare il tipo di eventi su cui porsi inascolto (oltre a supportare un numero maggiore di eventi). Ad oggi poll() edeprecata e sarebbe bene usare la nuova versione detta epoll().