UC3M

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2015 - Enero 2016

11.6.4. Procesado concurrente (hilos, mutex y variable de condición) de una tabla

Plan de trabajo

El presente ejercicio tiene por explorar las ventajas que ofrece la variable de condición a la hora de trabajar evitar esperas activas. La idea básica es procesar una estructura de datos larga y repartir el trabajo en varias hebras que potencialmente pueden ser reutilizadas. Hay 4 hebras que se encargan cada una de ellas de procesar 1/4 de la tabla de entrada.

El hilo realiza las siguientes operaciones:

  1. Se bloquea y espera a que haya algo que hacer. Para ello utiliza pthread_cond_wait.

  2. Cada hebra calcula el resultado de su parte de procesado de la tabla.

  3. La hebra escribe su parte parcial de la suma a la variable global sum.

  4. Al final, cuando termina la hebra, espera a que todas acaben con pthread_cond_wait.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/******************************************************************************
*
* compile with gcc -pthread *.c -o loops
* test with valgrind --tool=helgrind ./loops
*
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NTHREADS      4
#define ARRAYSIZE   100000000
#define ITERATIONS   ARRAYSIZE / NTHREADS

double  sum=0.0; 
double  a[ARRAYSIZE];
pthread_mutex_t sum_mutex;
pthread_cond_t threads_cond;

int work_pending=0; //No work
int work_done=0; //All done
void *do_work(void *tid) 
{
  int i, start, *mytid, end;
  double mysum=0.0;
  
  for(;;)
    {
      pthread_mutex_lock (&sum_mutex);
      while((work_pending==0))
	 { 
	   pthread_cond_wait(&threads_cond,&sum_mutex);    
	 }
      if(work_pending<0)
	{
	  pthread_mutex_unlock(&sum_mutex);
	  pthread_exit(NULL);
	 }
      work_pending--;       
      
      pthread_mutex_unlock(&sum_mutex);
      mytid = (int *) tid;
      start = (*mytid * ITERATIONS);
      end = start + ITERATIONS;
      printf ("\n[Thread %5d] To Work.Doing iterations \t%10d to \t %10d",*mytid,start,end-1); 
      for (i=start; i < end ; i++) 
	{
	  a[i] = i * 1.0;
	  mysum = mysum + a[i];
	}

	/* Lock the mutex and update the global sum, then exit */
      pthread_mutex_lock (&sum_mutex);
      sum = sum + mysum;
      work_done--;
      //printf ("\n[Thread %5d] Work done %d",*mytid,work_done); 
      pthread_cond_broadcast(&threads_cond);    
      pthread_mutex_unlock (&sum_mutex);
      
      pthread_mutex_lock (&sum_mutex);
      while(work_done!=0)
      {
	pthread_cond_wait(&threads_cond,&sum_mutex);    
      }
      pthread_mutex_unlock (&sum_mutex);
      
    }//end_for
  
  pthread_exit(NULL);
}


int main(int argc, char *argv[])
{
  int i;
  int tids[NTHREADS];
  pthread_t threads[NTHREADS];
  pthread_attr_t attr;

  /* Pthreads setup: initialize mutex and explicitly create threads in a
     joinable state (for portability).  Pass each thread its loop offset */
  pthread_mutex_init(&sum_mutex, NULL);
  pthread_cond_init(&threads_cond, NULL);
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  for (i=0; i<NTHREADS; i++) 
  {
    tids[i] = i;
    pthread_create(&threads[i], &attr, do_work, (void *) &tids[i]);
  }
  
  printf ("\n\n[MAIN][STEP 1] Let's Assign work to all threads\n");
  
  //Init the threads
  pthread_mutex_lock (&sum_mutex);
  work_pending=NTHREADS;
  work_done=NTHREADS; //
  pthread_cond_broadcast(&threads_cond);  
  pthread_mutex_unlock(&sum_mutex);
  printf ("\n\n[MAIN][STEP 2] Let's Wait for the result\n");
  //Wait for the result
  pthread_mutex_lock (&sum_mutex);
  while(work_done!=0)
    { //printf("Main to wait for the result %d",work_done);
      pthread_cond_wait(&threads_cond,&sum_mutex);  
      
    }
  pthread_mutex_unlock(&sum_mutex);
  
  printf ("\n\n[MAIN][STEP 3] Output. Sum= %e\n", sum);

  printf ("\n\n[MAIN][STEP 4] Let's Signal exit\n");
  
  pthread_mutex_lock(&sum_mutex);
  work_pending=-1; //To exit
  pthread_cond_broadcast(&threads_cond);  
  pthread_mutex_unlock(&sum_mutex);
  
  
    
  /* Wait for all threads to complete */ 
  for (i=0; i<NTHREADS; i++) {
    pthread_join(threads[i], NULL);
  }

  pthread_attr_destroy(&attr);
  pthread_cond_destroy(&threads_cond);
  pthread_mutex_destroy(&sum_mutex);
  pthread_exit (NULL);
}
 

Vamos a explorar el código de forma práctica para realizar ciertos cambios:

  1. Examine el código, compílelo con el gcc y ejecútelo. Calcule el tiempo de ejecución con 1 hebra, 2 hebras y 4 hebras.

  2. Modifique el código para que trabaje para que los hilos procesen varias tablas. Pista: Para realizar esta operación puede crear una estructura de datos donde pase como parámetro de entrada la tabla a las diferentes hebras del sistema, en un bucle.

  3. La solución utiliza una notificación en broadcast para despertar a los diferentes hilos. Cambiela para que utilice pthread_cond_signal.