Sincronización de hilos en POSIX: Mutex


Objetivos: Conocer los mecanismos de sincronización para acceso controlado a objetos compartidos.

Funciones POSIX para Sincronización
   Estas funciones incluyen mecanismos de exclusión mutua (mutex), mecanismos de señalización del cumplimiento de condiciones por parte de variables, y mecanismos de acceso de variables que se modifican en forma exclusiva, pero pueden ser leídas en forma compartida. Las funciones para el manejo de zonas de acceso exclusivo tienen el prefijo pthread_mutex
    Un mutex es una variable especial que puede tener estado tomado (locked) o libre (unlocked). Es como una compuerta que permite el acceso controlado. Si un hilo tiene el mutex entonces se dice que es el dueño del mutex. Si ningún hilo lo tiene se dice que está libre (o unclucked). Cada mutex tiene una cola de hilos que están esperando para tomar el mutex. El uso de mutex es eficiente, pero debería ser usado sólo cuando su acceso es solicitado por corto tiempo.

Función POSIX
Descripción
pthread_mutex_destroy
Destruye la variable (de tipo pthread_mutex_t) usada para manejo de exclusión mutua, o candado mutes
pthread_mutex_init
permite dar las condiciones iniciales a un candado mutex
pthread_mutex_lock
Permite solicitar acceso al mutex, el hilo se bloquea hasta su obtención
pthread_mutex_trylock
permite solicitar acceso al mutex,  el hilo retorna inmediatamente. El valor retornado indica si otro hilo lo tiene.
pthread_mutex_unlock
Permite liberar un mutex.

Creación e iniciación de mutex

#include <pthread.h>
int  pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);

Alternativamente podemos invocar:

pthread_mutex_t    mutex = PTHREAD_MUTEX_INITIALIZER;

lo cual inicializa el mutex con los atributos por omisión (default). Es el uso que daremos en este curso.
Es equivalente a invocar:
pthread_mutex_t mylock;
pthread_mutex_init(& mylock, NULL);

Destrucción de un mutex

#include <pthread.h>
int  pthread_mutex_destroy(pthread_mutex_t * mutex);

Si el mutex lo tenía otro hilo y éste es destruido, POSIX no define el comportamiento de mutex en esta situación.

Solicitud y liberación de un mutex

#include <pthread.h>
int  pthread_mutex_lock(pthread_mutex_t * mutex);
int  pthread_mutex_trylock(pthread_mutex_t * mutex);
int  pthread_mutex_unlock(pthread_mutex_t * mutex);

Con pthread_mutex_trylock el hilo siempre retorna, si la función es exitosa, se retorna 0 -como en los otros casos; si no se retornará EBUSY indicando que otro hilo tiene el mutex.

Ejemplo: Para proteger una zona crítica, usar:
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&mylock);
/* Sección crítica */
pthread_mutex_unlock(&mylock);

Ejemplo:
Sin Mutex Con Mutex

int counter=0;

/* Function C */
void functionC()
{

counter++

}
/* Note scope of variable and mutex are the same */
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter=0;

/* Function C */
void functionC()
{
pthread_mutex_lock( &mutex1 );
counter++
pthread_mutex_unlock( &mutex1 );
}
Ejemplo: couter.c : conjunto de funciones para acceder a un contador a través de múltiples hilos. countertest.c, los archivos necesarios los puede bajar de aquí. Vea el Makefine para generar ejecutables.
Multiplicación de vectores sin threads y con threads.
Vea pregunta 3 del primer certamen del año 2007.

Muchos llamados al sistema no son seguros de usar desde hilos (no son re-entrantes). strtok, rand, localtime, etc... Si necesitamos usar alguna de estas funciones, debemos considerarlas dentro de un código del tipo:
#include <pthread.h>
#include <stdlib.h>

int randsafe(double *ranp) {
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int error;

if (error = pthread_mutex_lock(&lock))
return error;
*ranp = (rand() + 0.5)/(RAND_MAX + 1.0);
return pthread_mutex_unlock(&lock);

}
Ejemplo: programa que calcula la suma de valores aleatorios de la función sin(), en varios hilos, los suma muestra su resultados.

Mecanismos de sincronización Reader-writer
  Estos mecanismos permiten acceso exclusivo para escritura pero accesos compartidos para lectura. También son conocidos como locks compartidos-exclusivos. La variable usada para proveer la sincronización en este caso es de tipo: pthread_rwlock_t. Así tres estados son posibles para este candado: cerrado en modo lectura, cerrado en modo escritura, y no cerrado. Sólo un hilo puede tener el candado en modo escritura, pero múltiples hilos pueden tener el candado en modo lectura. Cuando un hilo es bloqueado a la espera de liberación del candado en modo lectura, hilos subsiguientes deseando tomar el candado en modo lectura serán también bloqueados. Así se evita inanición de hilos esperando escribir.
Las funciones son:

Función POSIX
Descripción
pthread_rwlock_init
permite dar las condiciones iniciales a un candado de lectura y escritura.
pthread_rwlock_destry
Destruye la variable (de tipo pthread_rw_lock_t) usada para manejo de exclusión mutua, o candado read-write.
pthread_rwlock_wrlock
Permite solicitar acceso de escritura al candado, el hilo se bloquea hasta su obtención
pthread_rwlock_rdlock
permite solicitar acceso de lectura al candado,  el hilo accede si el candado está libre o si sólo ha sido concedido para lectura.
pthread_rwlock_unlock
Permite liberar un candado read-write.
othread_rwlock_tryrdlockPermite obtener el candado (lock) si está libre y retorna cero, en otro caso retorna EBUSY
pthread_rwlock_trywrlockPermite obtener el candado (lock) si está libre y retorna cero, en otro caso retorna EBUSY

Evitando Deadlock
Como se estudió en Sistemas Operativos, la mala programación o uso de estos mecanismos de sincronización pueden conducir a Bloqueos Indefinidos (deadlock). Una forma simple de evitar esta situación es asegurar que cada hilo pida acceso a las zonas exclusivas siguiendo un orden común en todos los hilos. Si tenemos los candados a, b y c, todos los hilos los deben pedir en ese orden. Otra opción es usar los tryxxlock, si es exitoso, OK; si no lo son se deben liberar locks previos, e intentar nuevamente.