Un thread se defineste ca o entitate de executie din interiorul unui proces,
compusa intr-un context si o secventa de instructiuni de executat. Un thread
executa o procedura sau o functie, in cadrul aceluiasi proces, concurent cu
alte thread-uri. Contextul procesului si zonele de date ale lui sunt utilizate
in comun de catre toate thread-urile pe care si le-a creat. Esenta de
reprezentare in memorie a unui thread este faptul ca singurul spatiu de memorie
ocupat exclusiv de el este o stiva proprie.

Un thread exista in cadrul unui proces. El este compus dintr-un context cu
atribute specifice, o structura utilizator, o stiva, o zona de date privata
si un set de instructiuni care se vor executa.

Un thread nu stie ce thread l-a creat si nu tine o lista cu thread-urile create.
Toate thread-urile create de un proces impart acelasi spatiu de adresare.

Thread-uri ce apartin aceluiasi proces impart:
    - majoritatea datelor
    - descriptorii fisierelor deschise
    - directorul curent
    - user si group id

Fiecare thread are:
    - thread ID
    - set de registrii, stack pointer
    - stiva pentru variabile locale, return addresses
    - signal mask
    - priority
    - return value: errno
    - functia pthread intoarce valoare "0" in caz de succes

Pentru a lucra cu thread-uri, ca si utilizator, trebuie sa avem in vedere:
- Crearea unui thread.
- Configurarea threadului creat.
- Planificarea accesului la procesor.
- Lansarea in executie.
- Cooperarea cu alte threaduri ale programului si/sau ale sistemului prin
  elemente de sincronizare.
- Terminarea activitatii unui thread.

-----------------------------------------------------------------
    Thread-uri Posix
Pentru thread-urile Posix trebuie folosit fisierul header <pthread.h>.

Pentru compilarea programelor care folosesc threaduri POSIX trebuie indicata
in comanda de compilare optiunea -lpthread sau -pthread (pentru a indica faptul
ca se foloseste biblioteca libpthread.so.0).

1. Crearea unui thread:
    int pthread_create(pthread_t *tid, pthread_attr_t *attr,
                void *(*func)(void*), void *arg);
unde:
- in tid se depune descriptorul threadului. Functia intoarce valoarea 0
  la succes si o valoare nenula (cod de eroare), in caz de esec;
- executia noului thread este descrisa de functia al carei nume este precizat
  prin parametrul func;
- parametrul attr permite specificarea unor atribute specifice threadului.
  Daca se specifica pentru attr valoarea NULL, sistemul va stabili valori
  implicite pentru atributele threadului.

2. Terminarea unui thread poate apare in urmatoarele situatii:
- Atunci cand se termina functia care descrie threadul.
- Din corpul functiei se poate comanda terminarea prin apelul functiei
    int pthread_exit(int *status);
- Terminarea comandata din exteriorul functiei threadului prin apelul
    int pthread_cancel(pthread_t tid);

3. Asteptarea terminarii unui thread
    int pthread_join(pthread_t tid, void **status);
- phread_join suspenda executia threadului apelant pana cand threadul
cu descriptorul tid isi incheie executia. Pentru un thread, se poate apela
aceasta functie o singura data.

------------------------------------------------------------------------
ex1. Crearea si terminarea unui thread
p1thread.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_TH 5

void *PrintM (void *threadid)
{
  long tid;
  tid = (long) threadid;
  printf ("Salut! Aici thread-ul #%ld!\n", tid);
  pthread_exit (NULL);
}

int main (int argc, char *argv[])
{
  pthread_t threads[NUM_TH];
  int rc;
  long t;
  for (t = 0; t < NUM_TH; t++) {
      printf ("In main: creez thread %ld\n", t);
      rc = pthread_create (&threads[t], NULL, PrintM, (void *) t);
      if (rc) {
        printf ("ERROR; codul de retur pentru pthread_create() este %d\n",
              rc);
        exit (-1);
      }
  }
  pthread_exit (NULL);
}

compilarea si rularea:
$gcc -pthread -Wall p1thread.c -o p1thread.o
$./p1thread.o


-------------------------------------------------------------------------
ex2. Transmiterea de parametrii thread-ului.

Sursa pentru thrPassArg1.c:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define NUM_TH 4

void *PrintM (void *threadArg)
{
  int *tid;
  tid = (int *) threadArg;
  //sleep (1);
  printf ("Functia PrintM executata de thread-ul %d.\n", *tid);
  pthread_exit (NULL);
}

int main (int argc, char *argv[])
{
  pthread_t threads[NUM_TH];
  int *tids[NUM_TH];//id thread-uri, parametru pasat functiei
  int t, rc;

  for (t = 1; t < NUM_TH + 1; t++) {
    tids[t] = (int *) malloc(sizeof(int));
    *tids[t] = t;
    printf ("Creez thread-ul %d\n", t);
    rc = pthread_create (&threads[t], NULL, PrintM, (void *)
                           tids[t]);
    if (rc) {
      printf ("ERROR; cod retur pentru pthread_create() - %d\n", rc);
      exit (-1);
    }
  }
  pthread_exit (NULL);
}

compilarea si rularea:
$gcc -Wall -pthread thrPassArg1.c -o thrPassArg1.o
$./thrPassArg1.o

un posibil rezultat:
Creez thread-ul 1
Creez thread-ul 2
Functia PrintM executata de thread-ul 1.
Creez thread-ul 3
Functia PrintM executata de thread-ul 2.
Creez thread-ul 4
Functia PrintM executata de thread-ul 3.
Functia PrintM executata de thread-ul 4.

-------------------------------
Transmiterea de parametrii prin intermediul unei structuri.
Sursa pentru programul thrPassArg2.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define NUM_TH 4

struct thread_data
{
  int thread_id;
  int n1;
  int n2;
};

struct thread_data thread_data_array[NUM_TH];

void *PrintM (void *threadArg)
{
  int taskid, sum, n1, n2;
  struct thread_data *my_data;

  sleep (1);
  my_data = (struct thread_data *) threadArg;
  taskid = my_data->thread_id;
  n1 = my_data->n1;
  n2 = my_data->n2;
  sum = n1 + n2;
  printf ("Thread %d, %d + %d = %d\n", taskid, n1, n2, sum);
  pthread_exit (NULL);
}

int main (int argc, char *argv[])
{
  pthread_t threads[NUM_TH];
  int rc, t, n1, n2;
  srand(time(NULL));

  for (t = 1; t < NUM_TH + 1; t++) {
    n1 = rand () % 100;
    n2 = rand () % 100 + 100;
    thread_data_array[t].thread_id = t;
    thread_data_array[t].n1 = n1;
    thread_data_array[t].n2 = n2;
    printf ("Creez thread-ul %d\n", t);
    rc = pthread_create (&threads[t], NULL, PrintM, (void *)
                         &thread_data_array[t]);
    if (rc) {
      printf ("ERROR; cod retur pentru pthread_create() - %d\n", rc);
      exit (-1);
      }
  }
  pthread_exit (NULL);
}

compilarea si executia programului:
$gcc -Wall -pthread thrPassArg2.c -o thrPassArg2.o
$./thrPassArg2.o

un posibil rezultat:
Creez thread-ul 1
Creez thread-ul 2
Creez thread-ul 3
Creez thread-ul 4
Thread 1, 83 + 186 = 269
Thread 2, 77 + 115 = 192
Thread 3, 93 + 135 = 228
Thread 4, 86 + 192 = 278

-------------------------------------------------------------------------
ex3. Sincronizarea thread-urilor folosind pthread_join()
Daca se doreste sincronizarea thread-urilo folosind pthread_join(), un
thread trebuie explicit creat ca si "joinable". Pentru aceasta trebuie:
- declarat un atribut pthread ca si variabila de tipul pthread_attr_t
- initializarea acestui atribut cu pthread_attr_init()
- setarea statusului atributului pthread_attr_setdetachstate()
- la terminare trebuie eliberate resursele folosind pthread_attr_destroy()

Sursa pentru problema p3thread.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>

#define NUM_TH 4

void *BusyWork (void *t) {
  int i;
  long tid;
  double res = 0.0;
  tid = (long) t;
  printf ("Start thread %ld...\n", tid);
  for (i = 0; i < 1000000; i++) {
    res += sin(i) * tan(i);
  }
  //sleep(2);
  printf ("Sfarsit thread %ld. Rezultatul este %e\n", tid, res);
  pthread_exit ((void *) t);
}

int main (int argc, char *argv[]) {
  pthread_t thread[NUM_TH];
  pthread_attr_t attr;
  int rc;
  long t;
  void *status;

  // Initializare thread si setare status
  pthread_attr_init (&attr);
  pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE);

  for (t = 0; t < NUM_TH; t++) {
    printf ("Proces main: se creaza thread-ul %ld\n", t);
    rc = pthread_create (&thread[t], &attr, BusyWork, (void *) t);
    if (rc) {
      printf ("ERROR; codul de retur pentru pthread_create() este %d\n", rc);
      exit (-1);
    }
  //sleep(2);
  }

  //Eliberez atributele si astept dupa celelalte thread - uri
  pthread_attr_destroy (&attr);
  for (t = 0; t < NUM_TH; t++) {
    rc = pthread_join (thread[t], &status);
    if (rc) {
      printf ("ERROR; codul de retur pentru pthread_join() este %d\n", rc);
      exit (-1);
    }
    printf("Proces main: am terminat join cu thread-ul %ld avand statusul %ld\n",
          t, (long) status);
    //sleep(1);
  }

  printf ("Proces main: gata!\n");
  pthread_exit (NULL);
}

compilarea si executia programului:
$gcc -pthread -Wall p3thread.c -o p3thread.o -lm
$./p3thread.o

pentru a vedea efectul join puteti comenta ultimul ciclu for din functia main().

----------------------------------------------------------------------
Probleme
1. Rezolvati o problema din lab5-6 folosind thread-uri.

2. Creati un program C care primeste ca si argumente nume de fisiere text si
le prelucreaza simultan folosind thread-uri. Programul transforma fisierele
text astfel incat toate cuvintele din fisiere sa inceapa cu litera
mare. Fisierele noi obtinute (cele ce contin cuvintele cu prima litera
capitalizata) vor primi acelasi nume ca si fisierele sursa dar la sfarsit se
adauga terminatia N. Se va crea un thread pentru prelucrarea fiecarui fisier.