miercuri, 14 decembrie 2011

Multi Threading pe Linux in C++


Multi poate v-ati intrebat cum e posibil sa creezi threaduri in c++. Intrebuintarea lor este foarte vasta, de la taskuri prestabilitie pana la sisteme de Timer/ programare paralela, divide et impera,etc. In principiu e o alternativa foarte buna la programarea top-down/bottom-up, lasand posibilitatea de executia a mai multor instructiuni in paralel. Sa spunem ca aveti un sistem de inregistrare intr-o baza de date la fiecare 5 minute, ce trebuie rulat in paralel cu programul principal. In acest caz este de recomandat sa impartiti sarcinile.

Observatie importanta: Threadurile nu sunt acelasi lucru cu Core-urile unui procesor. Chiar si pe single-coreuri threadurile pot rula in paralel, procesorul schimband instructiunile cu o viteza ce da senzatia de lucru simultan.

Alta observatie importanta este ca mai multe Threaduri pot imparti aceleasi resurse(cum ar fi zona de memorie, instructiuni, etc).
http://en.wikipedia.org/wiki/Thread_%28computer_science%29
Revenind la subiectul nostru, astazi voi prezenta o librarie extrem de folositoare: pthread
http://en.wikipedia.org/wiki/POSIX

       In prima parte a subiectului as vrea sa abordez baza programarii multithreading. Ultima parte va fi dedicata unei intrebari fundamentale: Lucrul cu parametrii si returnari pe aceste functii.




Bazele tehnicii de programare


In primul rand in cpp-ul nostru vom include libraria pthread
#include <pthread.h>

Cel mai important lucru de tinut minte este structura pthread_t .Aceasta reprezinta handleul unui thread/variabila corespunzatoare threadului.

Crearea unui handle:
pthread_t exemplu;

Crearea unui thread
Functia pthread_create - Crearea unui thread:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

pthread_t * thread
Dupa cum spuneam, este structura ce tine loc de handle al threadului.
Orice operatie asupra threadului(stergere,modificare, join,etc) va fi facuta prin acest identificator.

pthread_attr_t *attr
Structura ce inglobeaza atributele threadului. O voi detalia mai tarziu!

void *(*start_routine)(void*)
Functia, mai exact pointerul functiei apelate. Obligatoriu sa aiba acel prototip.

void *arg
Argumentele. Le voi detalia mai tarziu.

     Daca este creat,functia pthread_create() returneaza 0. Altfel este returnat codul erorii.
Coduri de erori:

[EAGAIN]
Memorie insuficienta sau a fost atins numarul maxim de threaduri admis per proces (PTHREAD_THREADS_MAX)

[EINVAL]
Valuarea structurii attr este invalida 

[EPERM]
Apelantul nu are permisiunea corespunzătoare pentru a seta parametrii necesari pentru programarea politicii de planificare
.

Exemplu:
#include <iostream>
#include <stdlib.h>
#include <pthread.h>


using namespace std;

static void *function(void *arg)
{
for(int i=1;i<10;i++)
cout<<"Kisses from a thread :D"<<endl;
return NULL;
}


int main()
{
pthread_t exemplu;
int result = pthread_create(&exemplu,NULL,function,NULL);
if(result==0)
{
cout<<"Apelare reusita"<<endl;
}
else
{
cout<<result<<endl; //Consultati erorile
}
/*Dupa cum vedeti functia nu a fost apelata in mod direct. De asemenea in timpul rularii functiei(afisarii) procedura main inca functioneaza scriind "Apelare reusita" si Final, nefiind blocata pana la finalizarea loopului. Astfel se pot executa 2 instructiuni in acelasi timp independent. Ganditi-va ce inseamna pentru listeningul unui port de exemplu...Pe un thread ascultare, pe altul timitere fara sa se blocheze unul pe altul pana la finalizarea unuia dintre ele */
cout<<"Final"<<endl;
return 0;
}
Blocarea unui thread

Functia pthread_join- asteptarea unui thread:
int pthread_join(pthread_t thread, void **value_ptr);

Aceasta functie blocheaza executia apelantului pana la finalizarea threadului apelant.

pthread_t thread
Acel handle de care v-am spus mai devreme(IDul threadului)- exemplu
value_ptr
La întoarcerea dintr-un apel apel cu un argument value_ptr non-NULL, valoarea transmisa pthread_exit este pusa la dispoziţie în locaţia de referinţă de către value_ptr

Daca este blocat,functia pthread_join() returneaza 0. Altfel este returnat codul erorii.
http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_join.html

De retinut este ca va recomand sa folositi pthread_exit pentru a curata datele, deoarece exista posibilitatea sa nu fie sterse automat!

Exemplu:

#include <iostream>
#include <stdlib.h>
#include <pthread.h>


using namespace std;

static void *function(void *arg)
{
for(int i=1;i<10;i++)
cout<<"Kisses from a thread :D"<<endl;
return NULL;
}


int main()
{
pthread_t exemplu;
int result = pthread_create(&exemplu,NULL,function,NULL);
if(result==0)
{
cout<<"Apelare reusita"<<endl;
}
else
{
cout<<result<<endl; //Consultati erorile
}
pthread_join(exemplu,NULL);
/*Fata de exemplul anterior, chiar daca cele 2 proceduri sunt executate concomitent, main este blocata pana la finalizarea threadului exemplu. Astfel, "Final" apare doar dupa afisarea celor 9 propozitii.

Daca aveti 2 threaduri, pthread_join nu le va bloca pe ambele, ci doar procedura apelanta(main). Cele 2 threaduri vor functiona normal in paralel*/
cout<<"Final"<<endl;
return 0;
}




Iesirea dintr-un Thread
void pthread_exit(void *value_ptr);
http://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread_exit.html

Restul: http://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread.h.html


Acum atributele...
Din start va spun ca trebuie sa aveti mare grija la atributul de dimensiune a Stackului. Threadurile tind sa ocupe foarte mult spatiu chiar daca nu este folosit. Pentru a seta dimensiunea stackului:

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

pthread_attr_t *attr - atributul din structura specifica
size_t stacksize - Dimensiunea stivei.

Returneaza 0 la succes sau eroare:
[EINVAL]

Valoarea stacksize e mai mica decat PTHREAD_STACK_MIN(de obicei 16KB) sau mai mare decat cea impusa de sistem

Ce contine structura de atribute:

size_t stacksize - Dimensiunea stivei

void *stackaddr - Adresa stivei-Folositoare cand vrem sa asignam stiva altui thread sau sa o copiem. Pointer void

size_t guardsize The thread's stack guard size. The default size is PAGESIZE bytes.

int detachstate - Starea threadului: created, joinable, deteched, etc

int contentionscope - Vizibilitatea threadului: PTHREAD_SCOPE_SYSTEM, PTHREAD_SCOPE_PROCESS

int inheritsched

int schedpolicy - Politica de scheduling SCHED_FIFO, SCHED_RR, SCHED_OTHER

struct sched_param schedparam - Parametrii de scheduling

Initializarea: De exemplu vrem ca orice thread ce e initializat in pthread_create cu aceasta structura sa aiba 16 KB Stack
pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); pthread_attr_setstacksize(&thread_attr, 16384 );

Ex:
nt pthread_attr_init (pthread_attr_t *attr); //Initializeaza structura
int pthread_attr_destroy(pthread_attr_t *attr);


int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);

int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);

int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);

int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);

int pthread_attr_getscope(const pthread_attr_t *attr, int *contentionscope);

int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);

int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);

int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);




Ex: #include <iostream>
#include <stdlib.h>
#include <pthread.h>
#define SMALL_STACK 16384

using namespace std;

static void *function(void *arg)
{
for(int i=1;i<10;i++)
cout<<"Kisses from a thread :D"<<endl;
return NULL;
}


int main()
{
pthread_attr_t thread_attr;
pthread_attr_init(&thread_attr);
pthread_attr_setstacksize(&thread_attr, SMALL_STACK);
pthread_t exemplu;
int result = pthread_create(&exemplu,&thread_attr,function,NULL);
if(result==0)
{
cout<<"Apelare reusita"<<endl;
}
else
{
cout<<result<<endl; //Consultati erorile
}
/*Daca veti face testul de memorie folosita veti vedea diferente uriase de resurse. Atentie! Aceeasi structura poate fi folosita de mai multe threaduri la creare, ea doar detine optiunile pe care le transmite threadurilor.
Parametrul se transmite prin referinta!!! */
cout<<"Final"<<endl;
return 0;
}




Parametrii si returnari


Problema se pune de multe ori asupra parametrilor si a returnarilor. Pentru inceput vreau sa va invat transmiterea parametrilor. Dupa cum ati obervat prototipul functiei apelate este void* f(void* p)


Deci atat returnarea cat si parametrii sunt transmisi prin referinta(adresa lor e defapt transmisa), adica, se pot modifica direct din pointer. Problema e cu cast-ul. Tipul void* poate fi transformat in orice tip de data dorim.. de exemplu:

void* c ;
int *p = (int *)c - Transformam pointer void in int

Sau :
int x;
void *p = &x;

Diferenta dintre tipuri este dimensiunea pe care o aloca.
De exemplu:

#include <iostream>
#include <stdlib.h>
#include <pthread.h>
using namespace std;

static void *function(void *arg)
{
int *par = (int *)arg; //Nu uitati...arg e un pointer...par tot pointer
for(int i=1;i<10;i++)
cout<<"Kisses from"<<" "<<*par<<endl; //Afisam valoarea valorii tinuta de pointerul par(valoarea lui //e o adresa)

return NULL;

}

int main()
{

pthread_t exemplu;
int var = 5;
int result = pthread_create(&exemplu,NULL,function,&var);

if(result==0)
{
cout<<"Apelare reusita"<<endl;
}
else
{
cout<<result<<endl; //Consultati erorile
}
cout<<"Final"<<endl;
return 0;

}

////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <stdlib.h>
#include <pthread.h>
using namespace std;

static void *function(void *arg)

{
char *c = (char *)arg //Nu uitati...arg e un pointer...par tot pointer
for(int i=1;i<10;i++)
cout<<"Kisses from"<<" "<<c<<endl; //Se va afisa: Kisses from a thread
return NULL;
}

int main()
{
pthread_t exemplu;
char *var = "a thread";
int result = pthread_create(&exemplu,NULL,function,var);//Defapt chiar daca nu e prin referinta, string e defapt un pointer, deci transmite adresa si nu valoare
if(result==0)
{
cout<<"Apelare reusita"<<endl;
}
else
{
cout<<result<<endl; //Consultati erorile
}

/*Transmiterea unui string*/
cout<<"Final"<<endl;
return 0;

}


////////////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <stdlib.h>
#include <pthread.h>
using namespace std;
static void *function(void *arg)
{
int *par = (int *)arg;
for(int i=0;i<4;i++)
cout<<"Kisses from"<<" "<<*(par+i)<<endl; //Se va afisa: Kisses from a 1,2,3,4,5
return NULL;

}

int main()
{
pthread_t exemplu;
int var[5]={1,2,3,4,5};
int result = pthread_create(&exemplu,NULL,function,&var);//Prin referinta se transmite decat adresa de inceput(elementului 0)

if(result==0)
{
cout<<"Apelare reusita"<<endl;
}
else
{
cout<<result<<endl; //Consultati erorile
}
/*Transmiterea unui vector*/
cout<<"Final"<<endl;
return 0;

}


O particularitate este daca vrem sa transmitem un array/chiar un array de diferite tipuri. Void * ne ofera posibilitatea de a transmite orice adresa si de a face cast dupa aceea.

In postarile urmatoare va voi invata despre pointerii functiilor(cum pot fi apelate indirect) astfel incat peste 2 dati o sa creem o clasa de Timer cu CallBack function si eventuri.

Un comentariu:

  1. As avea si eu o intrebare, cu ajutorul multi threading-ului pot deschide doua servere pe acelasi pc? Ma ajuta?

    RăspundețiȘtergere