miércoles, 28 de diciembre de 2011

Concurrencia - Mutex

En esta entrada hablaremos acerca de la concurrencia. En particular de los Mutex. Los mutex será el mecanismo que nos ayude en la sincronización  para proteger una sección crítica en nuestro código. Estas secciones críticas serán los datos a los que se acceda de forma concurrente. Por ejemplo si desde dos hilos diferentes leemos y escribimos en una variable global la ejecución puede ser deficiente.

Supongamos que tenemos dos hilos (h1 y h2) que leen de la variable 'valor' que inicialmente tiene un valor de 5. Si ambas desean aumentar el valor de esta variable puedo ocurrir lo siguiente:
    • El hilo h1 lee valor (5).
    • El hilo h2 lee valor (5).
    • El hilo h2 aumenta en 3 valor (5+3=8) y guarda.
    • El hilo h1 aumenta en 2 valor (5+2=7) y guarda.
Una vez finalizado esto valor es igual a 7 cuando debería ser igual a 10. Para evitar esto empleamos como mecanismo de sincronización los mutex. Los mutex nos ayudaran a bloquear los accesos a datos, mientras un proceso ligero (hilo) este accediendo a una sección crítica otro proceso no podrá acceder a ella y esperará a que liberen el mutex para acceder a ella. Las variables mutex son del tipo pthread_mutex_t los métodos para emplear los mutex son los siguientes:
  • int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t * attr);
    • Inicializa el mutex
  • int pthread_mutex_destroy(pthread_mutex_t *mutex) ;
    • Destruye el mutex (lo elimina de la memoria).
  • int pthread_mutex_lock(pthread_mutex_t *mutex);
    • Bloquea el mutex si no lo tiene nadie. Si alguien tiene bloqueado el mutex el proceso espera hasta que el proceso que lo tiene bloqueado lo libera.
  • int pthread_mutex_unlock(pthread_mutex_t *mutex);
    • Libera el mutex.
Existe también un tipo de mecanismo de sincronización llamado variables condición que veremos en una entrada posterior. Además también existen los llamados semaforos, sin embargo es preferible emplear mutex y variables condición (en conjunto). Los semaforos también se verán en próximas entradas. 

Para ver el uso de los mutex realizaré un ejemplo con el famoso problema de concurrencia de lectores-escritores. En este problema existen procesos escritores (modifican datos) y lectores que los leen. Las especificaciones del problema son: tan solo puede haber 1 escritor escribiendo, para escribir no puede haber ningún lector. Puede haber cualquier número de lectores siempre y cuando no se esté escribiendo.  Como crear hilos en una entrada anterior

    
int dato = 5; /* recurso */
int lectores = 0; /* numero de lectores, es también un recurso, tan solo para los lectores */
pthread_mutex_t mutexG; /* controlar el acceso a dato */
pthread_mutex_t mlectores; /* controla acceso n_lectores */

int main(int argc, char *argv[]) {
 pthread_t th1, th2, th3, th4;
 pthread_mutex_init(&mutexG, NULL);
 pthread_mutex_init(&mlectores, NULL);
 pthread_create(&th1, NULL, (void*)Lector, NULL);
 pthread_create(&th2, NULL, (void*)Escritor, NULL);
 pthread_create(&th3, NULL, (void*)Lector, NULL);
 pthread_create(&th4, NULL, (void*)Escritor, NULL);
 
 pthread_join(th1, NULL);
 pthread_join(th2, NULL);
 pthread_join(th3, NULL);
 pthread_join(th4, NULL);
 
 pthread_mutex_destroy(&mutexG);
 pthread_mutex_destroy(&mlectores);
 
 exit(0);
}

/*
 * Código del escritor
 */
void Escritor(void) { 
 pthread_mutex_lock(&mutexG);
 
 dato = dato + 2; /* aumenta el dato*/
 
 pthread_mutex_unlock(&mutexG);
 pthread_exit(0);
}

/*
 * Código del lector
 */
void Lector(void) { 
 pthread_mutex_lock(&mlectores);
 lectores++;
 if (lectores == 1) 
  pthread_mutex_lock(&mutexG);
 pthread_mutex_unlock(&mlectores);
 printf("%d\n", dato); /* leemos el dato dato */
 pthread_mutex_lock(&mlectores);
 lectores--;
 if (lectores == 0) 
  pthread_mutex_unlock(&mutexG);
 pthread_mutex_unlock(&mlectores);
 pthread_exit(0);
}


Podemos ver como se bloquea el mutex general cuando se va a escribir. Esto hace que mientras que se esté escribiendo no se pueda realizar ninguna otra acción y tan solo exista 1 escritor escribiendo a la vez. Por otra parte en los lectores tenemos un mutex que bloquea el acceso a la variable que cuenta el número de lectores. Si es el primer lector bloquea el mutex general para que no se pueda escribir mientras se está leyendo. Cuando el último lector a finalizado de leer libera el mutex para que se pueda comenzar a escribir.

Con esto espero que haya quedado claro. Si tenéis alguna duda podéis escribir un comentario.

7 comentarios:

  1. gracias pero no encuentro su "post promesa" de semáforos por ningún lado, si existe agradecería si pudiese facilitarme el enlace.

    ResponderEliminar
    Respuestas
    1. Estos Post fueron realizados para ayudar a compañeros de clase. En esos momentos me pidieron otra serie de post, pero me lo apunto a mi lista de cosas pendientes. En cuanto termine los exámenes me pongo a ello. Gracias por tu comentario =)

      Eliminar
    2. gracias a ti por estos posts, me han sido de mucha utilidad

      Eliminar
  2. Gracias por este post. Aunque me gustaría que hicieses el de semáforos

    ResponderEliminar