image/svg+xml

Hilos

Programaci贸n concurrente

Primero se analizar谩 un programa que implementa hilos para calcular la suma de todos los elementos de una matriz $N\times N$. El programa realiza lo siguiente:

  1. Inicializa la matriz $N\times N$
  2. Crea $N$ threads para calcular la suma de los elementos de 1 fila y almacena el valor en un arreglo
  3. Une los threads y calcula la suma de los elementos del arreglo de suma.
/**** C4.1.c file: compute matrix sum by threads ***/
/**
 * REF: Systems Programming in Unix/Linux, K.C. Wang, editorial 
 * Springer Nature, 2018. Chapter 4 Concurrent Programming, page 147.
 * ISBN: 978-3-319-92428-1 ISBN: 978-3-319-92429-8 (eBook).
 *
 *   gcc c4.1.c -pthread
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>

#define N 4
int A[N][N], sum[N];

void *func(void *arg) // threads function
{
  int j;
  long row;
  pthread_t tid = pthread_self();// get thread ID number
  row=(long)arg;
  printf("Thread %d [%lu] computes sum of row %d\n", row, tid, row);
  for (j=0; j<N; j++)            // compute sum of A[row]in global sum[row]
    sum[row] += A[row][j];
  printf("Thread %d [%lu] done: sum[%d] = %d\n",
          row, tid, row, sum[row]);
  pthread_exit((void*)0);        // thread exit: 0=normal termination
}/*end func()*/

int main (int argc, char *argv[])
{
  pthread_t thread[N]; // thread IDs
  long i;
  int j, r, total = 0;
  void *status;
  
  printf("Main: initialize A matrix\n");
  for (i=0; i<N; i++){
    sum[i] = 0;
    for (j=0; j<N; j++){
      A[i][j] = i*N + j + 1;
      printf("%4d ", A[i][j]);
    }
    printf("\n");
  }
  
  printf("Main: create %d threads\n", N);
  for(i=0; i<N; i++) {
    pthread_create(&thread[i], NULL, func, (void *)i);
  }
  
  printf("Main: try to join with threads\n");
  for(i=0; i<N; i++) {
    pthread_join(thread[i], &status);
    printf("Main: joined with %d [%lu]: status=%lu\n",
           i, thread[i], (long)status);
  }
  
  printf("Main: compute and print total sum: ");
  for (i=0; i<N; i++)
    total += sum[i];
  printf("total = %d\n", total);
  pthread_exit(NULL);
}/*end main()*/

Primero se define el tama帽o de la matriz, el arreglo matricial y el arreglo de suma:

#define N 4
int A[N][N], sum[N];

Iniciando con la funci贸n main, primero se declaran las variables necesarias:

int main (int argc, char *argv[])
{
  pthread_t thread[N]; // thread IDs
  long i;
  int j, r, total = 0;
  void *status;

Luego se inicializa la matriz con ciertos valores iniciales diferentes de $0$ y el vector de suma en $0$s:

  printf("Main: initialize A matrix\n");
  for (i=0; i<N; i++){
    sum[i] = 0;
    for (j=0; j<N; j++){
      A[i][j] = i*N + j + 1;
      printf("%4d ", A[i][j]);
    }
    printf("\n");
  }

Se crean los threads necesarios en el arreglo de thread[], que van a ejecutar la funci贸n func, que se mostrar谩 posteriormente, como argumento a dicha funci贸n se proporciona el n煤mero de fila como un puntero a void:

  printf("Main: create %d threads\n", N);
  for(i=0; i<N; i++) {
    pthread_create(&thread[i], NULL, func, (void *)i);
  }

La funci贸n func, se muestra a continuaci贸n:

void *func(void *arg) // threads function
{
  int j;
  long row;
  pthread_t tid = pthread_self();// get thread ID number
  row=(long)arg;
  printf("Thread %d [%lu] computes sum of row %d\n", row, tid, row);
  for (j=0; j<N; j++)            // compute sum of A[row]in global sum[row]
    sum[row] += A[row][j];
  printf("Thread %d [%lu] done: sum[%d] = %d\n",
          row, tid, row, sum[row]);
  pthread_exit((void*)0);        // thread exit: 0=normal termination
}/*end func()*/

Dicha funci贸n tiene como valor de retorno un puntero a void y recibe como par谩metro un n煤mero de fila de la matriz, imprime su id de thread y realiza la suma de los elementos de la fila correspondiente de la matriz y almacena el resultado en el vector de suma. Luego imprime la suma y retorna con pthread_exit() retornando $0$ si todo se ejecut贸 correctamente.

Luego se realiza la uni贸n de los threads, esto significa esperar por la finalizaci贸n de los hilos para continuar. Luego se imprime su est谩tus de retorno:

  printf("Main: try to join with threads\n");
  for(i=0; i<N; i++) {
    pthread_join(thread[i], &status);
    printf("Main: joined with %d [%lu]: status=%lu\n",
           i, thread[i], (long)status);
  }

Finalmente se realiza la suma de los elementos en el vector de suma sum[] y se imprime el resultado:

  printf("Main: compute and print total sum: ");
  for (i=0; i<N; i++)
    total += sum[i];
  printf("total = %d\n", total);
  pthread_exit(NULL);
}/*end main()*/

Compilando gcc c4.1.c -pthread y ejecutando el programa ./a.out, se obtiene la siguiente salida:

Main: initialize A matrix
   1    2    3    4
   5    6    7    8
   9   10   11   12
  13   14   15   16
Main: create 4 threads
Thread 0 [140214514730752] computes sum of row 0
Thread 0 [140214514730752] done: sum[0] = 10
Main: try to join with threads
Thread 1 [140214506338048] computes sum of row 1
Thread 3 [140214489552640] computes sum of row 3
Thread 1 [140214506338048] done: sum[1] = 26
Thread 3 [140214489552640] done: sum[3] = 58
Thread 2 [140214497945344] computes sum of row 2
Thread 2 [140214497945344] done: sum[2] = 42
Main: joined with 0 [140214514730752]: status=0
Main: joined with 1 [140214506338048]: status=0
Main: joined with 2 [140214497945344]: status=0
Main: joined with 3 [140214489552640]: status=0
Main: compute and print total sum: total = 136

Condiciones de carrera

Cuando se ejecutan varios hilos dentro de un proceso, estos comparten en el mismo espacio de direcciones de memoria y si en su programa modifican las mismas variables, pueden ocurrir fen贸menos como condiciones de carrera, que es cuando el resultado de una operaci贸n, depende del orden de ejecuci贸n de los hilos.

Las condiciones de carrera en general se deben evitar, programando para intentar que sean inexistentes. En el caso del programa anterior, no ocurre esto porque al ser el resultado la suma, no importa el orden en que se terminaron de ejecutar.

Sincronizaci贸n de hilos

Al hacer operaciones con hilos, tambi茅n es posible que estos necesiten cooperar entre s铆, por se hacen necesarios mecanismos de sincronizaci贸n y reglas de uso de datos compartidos.

Mutex Locks

Los candados Mutex se llaman as铆 por Mutual Exclusion y el mecanismo consiste en bloquear los datos de uso compartido mientras un hilo lo est谩 utilizando. Cuando deja de utilizarlo, desbloquea el candado y otros hilos pueden intentar acceder a 茅l.

PThread implementa los candados mutex de la siguiente forma:

  1. Inicializaci贸n de variable pthread_mutex_t
    • PTHREAD_MUTEX_INITIALIZER: Inicializaci贸n de forma est谩tica con los valores por defecto.

        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
      
    • pthread_mutex_init(pthread_mutex_t *m,pthread_mutexattr_t *attr): Funci贸n inicializadora de mutex

        pthread_mutex_init(mutex,NULL);
      

      Es com煤n utilizar NULL como *attr para que se implementen los atributos (configuraciones) por defecto.

  2. Utilizaci贸n de variables mutex

     pthread_mutex_lock(&mutex);     //bloqueo de mutex
     mutex = ...                     //acceso a variable mutex
     pthread_mutex_unlock(&mutex);   //desbloqueo de mutex
    

    Cuando el hilo intenta bloquear el candado con pthread_mutex_lock(), si est谩 disponible, se bloquea la variable, en caso de que no, se bloque al thread y se pone en la cola de espera del mutex.

  3. Destrucci贸n de las variables mutex

     pthread_mutex_destroy(mutex);     //bloqueo de mutex
     pthread_mutex_exit(NULL);   //desbloqueo de mutex
    

Se conoce como Regi贸n Cr铆tica (CR) a las ejecuciones que s贸lo pueden ser ejecutadas por 1 hilo a la vez.

En el ejemplo anterior las sumas de cada hilo, correspondientes a cada fila de la matriz, se almacenaban en su propia variable dentro del arreglo de suma sum[], a continuaci贸n se muestra un ejemplo del mismo programa pero donde los hilos modifican todos a una variable global total:

/** C4.3.c: matrix sum by threads with mutex lock **/
/**
 * REF: Systems Programming in Unix/Linux, K.C. Wang, editorial 
 * Springer Nature, 2018. Chapter 4 Concurrent Programming, page 152.
 * ISBN: 978-3-319-92428-1 ISBN: 978-3-319-92429-8 (eBook).
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>

#define N 4
int A[N][N];
int total = 0;        // global total
pthread_mutex_t *m;   // mutex pointer
void *func(void *arg) // working thread function
{
  int i,sum=0;
  long row;
  pthread_t tid = pthread_self(); // get thread ID number
  row=(long)arg;
  printf("Thread %d [%lu] computes sum of row %d\n", row, tid, row);
  for (i=0; i<N; i++)             // compute partial sum of A[row]in
    sum += A[row][i];
  printf("Thread %d [%lu] update total with %d : ", row, tid, sum);
  pthread_mutex_lock(m);
  total += sum;                   // update global total inside a CR
  pthread_mutex_unlock(m);
  printf("total = %d\n", total);
}

int main (int argc, char *argv[])
{
  pthread_t thread[N];
  long i;
  int j, r;
  int sum[N];
  void *status;
  printf("Main: initialize A matrix\n");
  for (i=0; i<N; i++){
    sum[i] = 0;
    for (j=0; j<N; j++){
      A[i][j] = i*N + j + 1;
      printf("%4d ", A[i][j]);
    }
    printf("\n");
  }
  // create a mutex m
  m = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
  pthread_mutex_init(m, NULL); // initialize mutex m
  printf("Main: create %d threads\n", N);
  for(i=0; i<N; i++) {
    pthread_create(&thread[i], NULL, func, (void *)i);
  }
  printf("Main: try to join with threads\n");
  for(i=0; i<N; i++) {
    pthread_join(thread[i], &status);
    printf("Main: joined with %d [%lu]: status=%lu\n",
           i, thread[i], (long)status);
  }
  printf("Main: total = %d\n", total);
  pthread_mutex_destroy(m); // destroy mutex m
  pthread_exit(NULL);
}/*end main()*/

Deadlocks

Al usar candados, pueden ocurrir problemas que impidan la correcta ejecuci贸n del programa. Un error muy com煤n es el deadlock que ocurre cuando hilos o en el caso del kernel, procesos, se bloquean todos esperando a que el otro termine su tarea para poder continuar.

Thread T1 Thread T2
lock(m1); lock(m2);
... ...
lock(m2) lock(m1)

Ambos threads se bloquean mutuamente y no hay salida, esto es un deadlock.

Esto se debe evitar en la programaci贸n concurrente, pero tambi茅n existen mecanismos de detecci贸n, prevenci贸n y recuperaci贸n de deadlocks:

Variables de condici贸n

Para la colaboraci贸n entre hilos, se implementan variables de condici贸n que permiten el bloqueo y desbloqueo de mutex entre hilos, por medio de condiciones. PThread las implementa de la siguiente forma:

  1. Inicializaci贸n de variable pthread_cond_t
    • PTHREAD_COND_INITIALIZER: Inicializaci贸n de forma est谩tica con los atributos por defecto.

        pthread_cond_t condition = PTHREAD_COND_INITIALIZER;
      
    • pthread_cond_init(pthread_cond_t *con,pthread_condattr_t *attr): Inicializaci贸n de forma din谩mica.

        pthread_cond_init(condition,NULL);
      

      Es com煤n utilizar NULL como *attr para que se implementen los atributos (configuraciones) por defecto.

  2. Utilizaci贸n de variables de condici贸n

     pthread_mutex_lock(&mutex);     //bloqueo de mutex
     mutex = ...                     //acceso a variable mutex
     //uso de variable de condici贸n para esperar o comunicarse con otro(s) hilo(s)
     pthread_mutex_unlock(&mutex);   //desbloqueo de mutex
    

    Las funciones que pueden utilizar las variables de condici贸n para interaccionar con otros hilos son las siguientes:

    • pthread_cond_wait(condition,mutex): Bloquea el hilo que la utiliza hasta que se se帽alice la condici贸n condition. Esta funci贸n deber铆a ser utilizada mientras mutex est谩 bloqueada, puesto que libera al mutex mientras est茅 esperando y cuando llega la se帽al en condition, desbloquea el hilo y bloquea a mutex de nuevo.
    • pthread_cond_signal(condition): Se帽aliza (activa) una variable de condici贸n que est谩 siendo esperada (wait) en otro thread. Deber铆a llamarse desde el hilo que ya termin贸 su operaci贸n con la variable mutex, para que el thread que espera continue.
    • pthread_cond_broadcast(condition): Desbloquea todos los hilos que est谩n bloqueados en la variable de condici贸n condition. As铆, todos los hilos desbloqueados, competir谩n por la el mutex.

A continuaci贸n se muestra un ejemplo de productor-consumidor implementando mutex y variables de condici贸n. Los buffers se implementan como buffers circulares.

/* C4.4.c: producer-consumer by threads with condition variables */
/**
 * REF: Systems Programming in Unix/Linux, K.C. Wang, editorial 
 * Springer Nature, 2018. Chapter 4 Concurrent Programming, page 158.
 * ISBN: 978-3-319-92428-1 ISBN: 978-3-319-92429-8 (eBook).
 *
 *   gcc c4.4.c -pthread
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NBUF	5
#define N	10

// shared global variables
int buf[NBUF];                    // circular buffers
int head, tail;                   // indices
int data;                         // number of full buffers
pthread_mutex_t mutex;            // mutex lock
pthread_cond_t emptyBuf, fullBuf; // condition variables

int init()
{
  head = tail = data = 0;
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&fullBuf, NULL);
  pthread_cond_init(&emptyBuf, NULL);
}

void *producer()
{
  int i;
  pthread_t me = pthread_self();
  for (i=0; i<N; i++){              // try to put N items into buf[ ]
    pthread_mutex_lock(&mutex);     // lock mutex
    if (data == NBUF){
      printf ("producer %lu: all bufs FULL: wait\n", me);
      pthread_cond_wait(&emptyBuf, &mutex); // wait
    }
    buf[head++] = i+1;              // item = 1,2,..,N
    head %= NBUF;                   // circular bufs
    data++;                         // inc data by 1
    printf("producer %lu: data=%d value=%d\n", me, data, i+1);
    pthread_mutex_unlock(&mutex);   // unlock mutex
    pthread_cond_signal(&fullBuf);  // unblock a consumer, if any
  }
  printf("producer %lu: exit\n", me);
}/*end producer()*/

void *consumer()
{
  int i, c;
  pthread_t me = pthread_self();
  for (i=0; i<N; i++) {
    pthread_mutex_lock(&mutex);            // lock mutex
    if (data == 0) {
      printf ("consumer %lu: all bufs EMPTY: wait\n", me);
      pthread_cond_wait(&fullBuf, &mutex); // wait
    }
    c = buf[tail++];                       // get an item
    tail %= NBUF;
    data--;                                // dec data by 1
    printf("consumer %lu: value=%d\n", me, c);
    pthread_mutex_unlock(&mutex);          // unlock mutex
    pthread_cond_signal(&emptyBuf);        // unblock a producer, if any
  }
  printf("consumer %lu: exit\n", me);
}/*end consumer()*/

int main ()
{
  pthread_t pro, con;
  init();
  printf("main: create producer and consumer threads\n");
  pthread_create(&pro, NULL, producer, NULL);
  pthread_create(&con, NULL, consumer, NULL);
  printf("main: join with threads\n");
  pthread_join(pro, NULL);
  pthread_join(con, NULL);
  printf("main: exit\n");
}/*end main()*/

Sem谩foros

Un sem谩foro es mecanismo general para sincronizar procesos. Tiene un contador y una cola:

struct sem{
    int value;              //valor del contador
    struct process *queue   //cola de procesos
}s;

Para que el sem谩foro funcione, necesita ser iniciado con un valor inicial de contador y una cola de espera vac铆a. Los sem谩foros s贸lo pueden ser operados por una entidad de ejecuci贸n a la vez, es decir, en una regi贸n cr铆tica. Adem谩s, sus procesos deben ser indivisibles y primitivos.

Las operaciones principales de los sem谩foros son P() y V():

void P(sem *s) {
    s.value -= 1;
    if (s.value < 0)
        BLOCK(s);
}

void V(sem *s) {
    s.value += 1;
    if (s.value <= 0)
        SIGNAL(s);
}

Donde BLOCK(s) bloquea el proceso que llama y lo coloca en la cola de espera del sem谩foro y SIGNAL(s) desbloquea un proceso de la cola de espera del sem谩foro.

Los sem谩foros a diferencia de las variables de condici贸n, manipulan el contador para tomar decisiones, en las variables de condici贸n se requiere un mutex lock. El sem谩foro con valor inicial de 1, puede ser utilizado como lock y los de otros valores para cooperaci贸n. Entonces los sem谩foros son m谩s vers谩tiles.

PThread en sus nuevas versiones soporta sem谩foros POSIX 1003.1b, con las siguientes funciones:

Implementando este sem谩foro para resolver el problema anterior, se tiene lo siguiente:

/* producer_consumer_semaphores.c: producer-consumer by threads 2 with semaphores */
/** * REF: Systems Programming in Unix/Linux, K.C. Wang, editorial
 * Springer Nature, 2018. Chapter 4 Concurrent Programming.
 * ISBN: 978-3-319-92428-1 ISBN: 978-3-319-92429-8 (eBook) and
 * Sistemas Operativos Diseho e ImplementaciOn, Andrew S. Tanenbaum,
 * Albert S. Whoodhull, editorial Prentice Hall, 1998. Chapter 2
 * Procesos, page 66.
 * gcc prod_cons_with_semaphores.c -pthread
 * 0 bien, gcc -lpthread -lrt prod_cons_with_semaphores.c 12
 */


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <assert.h>

#define NBUF 5
#define N 10

// shared global variables
int buf[NBUF];                      // circular buffers
int head, tail;                     // indices
int data;                           // number of full buffers
sem_t sem_mutex,sem_empty,sem_full; // semaphores


int init()
{
    head = tail = data = 0;
    sem_init(&sem_mutex,0,1);
    sem_init(&sem_empty,0,NBUF);
    sem_init(&sem_full,0,0);
}

void *producer()
{
    int i;
    pthread_t me = pthread_self();
    for (i=0; i<N; i++){	        // try to put N items into buf[ ]
        if (data == NBUF){
            printf ("producer %lu: all bufs FULL: wait\n", me);
        }
        sem_wait(&sem_empty);
        sem_wait(&sem_mutex);
        buf[head++] = i+1;	        // item = 1,2,..,N
        head %= NBUF;	            // circular bufs
        data++;	                    // inc data by 1
        printf("producer %lu: data=%d value.%d\n", me, data, i+1);
        sem_post(&sem_mutex);
        sem_post(&sem_full);
    }
    printf("producer %lu: exit\n", me);
}/*end producer()*/

void *consumer()
{
    int i, c;
    pthread_t me = pthread_self();
    for (i=0; i<N; i++) {
        if (data == 0) {
            printf ("consumer %lu: all bufs EMPTY: wait\n", me);
        }
        sem_wait(&sem_full);
        sem_wait(&sem_mutex);
        c = buf[tail++];	        // get an item
        tail %= NBUF;
        data--;	                    // dec data by 1
        printf("consumer %lu: yalue.%d\n", me, c);
        sem_post(&sem_mutex);
        sem_post(&sem_empty);
    }
    printf("consumer %lu: exit\n", me);
}/*end consumer()*/

int main ()
{
    pthread_t pro, con;
    init();
    printf("main: create producer and consumer threads\n");
    pthread_create(&pro, NULL, producer, NULL);
    pthread_create(&con, NULL, consumer, NULL);
    printf("main: join with threads\n");
    pthread_join(pro, NULL);
    pthread_join(con, NULL);
    sem_destroy(&sem_mutex);
    sem_destroy(&sem_empty);
    sem_destroy(&sem_full);
    printf("main: exit\n");
}/*end main()*/

Barreras

Cuando se hace la operaci贸n join, se espera a que los hilos hijos terminen para que el principal continue. Esto com煤nmente se utiliza para crear nuevos hilos que contin煤en con la siguiente etapa del proceso. En ese caso, a veces es m谩s eficiente continuar con los mismos hilos hijos, para no tener que crear nuevos.

Para implementar eso, se tienen varios mecanismos, pero el que utiliza PThreads es el mecanismo de barrera. Donde los hilos hijos llaman a una funci贸n de espera de barrera en alg煤n punto de su ejecuci贸n y cuando el n煤mero especificado de hilos llega, todos contin煤an con su ejecuci贸n de nuevo.

En PThread se implementa de la siguiente forma:

  1. pthread_barrier_t: Objeto barrera

     pthread_barrier_t barrier;
    
  2. Inicializaci贸n del objeto barrera:

     pthread_barrier_init(&barrier, NULL, nthreads);
    

    Se utiliza com煤nmente los atributos por defecto (pasando NULL)

  3. Implementaci贸n en hilos hijos

     ... // C贸digo anterior a la barrera
        
     pthread_barrier_wait(&barrier)
        
     ... // C贸digo posterior a la barrera
    

A continuaci贸n se muestra el c贸digo utilizado, implementando barreras para resolver un problema de eliminaci贸n Gaussiana.

/** C4.5.c: Gauss Elimination with Partial Pivoting **/
/**
 * REF: Systems Programming in Unix/Linux, K.C. Wang, editorial 
 * Springer Nature, 2018. Chapter 4 Concurrent Programming, page 162.
 * ISBN: 978-3-319-92428-1 ISBN: 978-3-319-92429-8 (eBook).
 *
 *   gcc c4.5.c -pthread
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <assert.h>

#define N	4
double A[N][N+1];
pthread_barrier_t barrier;

int print_matrix()
{
  int i, j;
  printf("------------------------------------\n");
  for(i=0; i<N; i++){
    for(j=0;j<N+1;j++)
      printf("%6.2f ", A[i][j]);
    printf("\n");
  }
}/*end print_matrix()*/

void *ge(void *arg) // threads function: Gauss elimination
{
  int i, j, prow;
  int k;
  long myid=(long)arg;
  double temp, factor;
  for(i=0; i<N-1; i++){
    if (i == myid){
      printf("partial pivoting by thread %d on row %d: ", myid, i);
      temp = 0.0; prow = i;
      for (j=i; j<=N; j++){
        if (fabs(A[j][i]) > temp){
          temp = fabs(A[j][i]);
          prow = j;
        }
      }
      printf("pivot_row=%d pivot=%6.2f\n", prow, A[prow][i]);
      if (prow != i){ // swap rows
        for (j=i; j<N+1; j++){
          temp = A[i][j];
          A[i][j] = A[prow][j];
          A[prow][j] = temp;
        }
      }
    }/*if(i==myid)*/
    // wait for partial pivoting done
    pthread_barrier_wait(&barrier);
    for(j=i+1; j<N; j++){
      if (j == myid){
        printf("thread %d do row %d\n", myid, j);
        factor = A[j][i]/A[i][i];
        for (k=i+1; k<=N; k++)
          A[j][k] -= A[i][k]*factor;
        A[j][i] = 0.0;
      }
    }
    // wait for current row reductions to finish
    pthread_barrier_wait(&barrier);
    if (i == myid)
      print_matrix();
  }/*end for(i=0;)*/
}/*end ge()*/

int main(int argc, char *argv[])
{
  long i;
  int j;
  double sum;
  pthread_t threads[N];

  printf("main: initialize matrix A[N][N+1] as [A|B]\n");
  for (i=0; i<N; i++)
    for (j=0; j<N; j++)
      A[i][j] = 1.0;
  for (i=0; i<N; i++)
    A[i][N-i-1] = 1.0*N;
  for (i=0; i<N; i++){
    A[i][N] = 2.0*N - 1;
  }
  print_matrix(); // show initial matrix [A|B]

  pthread_barrier_init(&barrier, NULL, N); // set up barrier

  printf("main: create N=%d working threads\n", N);
  for (i=0; i<N; i++){
    pthread_create(&threads[i], NULL, ge, (void *)i);
  }
  printf("main: wait for all %d working threads to join\n", N);
  for (i=0; i<N; i++){
    pthread_join(threads[i], NULL);
  }
  printf("main: back substitution : ");
  for (i=N-1; i>=0; i--){
    sum = 0.0;
    for (j=i+1; j<N; j++)
      sum += A[i][j]*A[j][N];
    A[i][N] = (A[i][N]- sum)/A[i][i];
  }
  // print solution
  printf("The solution is :\n");
  for(i=0; i<N; i++){
    printf("%6.2f ", A[i][N]);
  }
  printf("\n");
}/*end main()*/

Hilos en Linux

En linux, los hilos no son m谩s que procesos que comparten recursos. Ambos, hilos y procesos son creados por medio de la llamada a sistema clone():

int clone (int (*fn)(void *), void *child_stack, int flags, void *arg)

Es como una funci贸n de creaci贸n de hilos. Crea un proceso hijo para ejecutar una funci贸n fn(arg) con un child_stack. flags especif铆ca c贸mo los recursos ser谩n compartidos entre el padre y el hijo como uno de los siguientes:

Para cualquiera de las banderas, los procesos padre e hijo comparten exactamente el mismo recurso, no una copia de 茅l.

Si no se especifica ning煤na bandera, el proceso hijo recibe una copia separada del recurso. O sea que los cambios hechos no se veran afectados en el otro recurso.

El campo arg son los argumentos a la funci贸n fn.

El kernel Linux tiene fork como un wrapper de clone en forma de llamada a sistema. As铆, el kernel se encarga de especificar las banderas apropiadas.