Problema de los filósofos comensales en C: implementación con hilos y mutex
Problema de los filósofos comensales
A continuación se presenta una implementación en lenguaje C del clásico problema de los filósofos comensales, utilizando hilos POSIX (pthreads) y mutex para controlar el acceso a los tenedores compartidos.
Variables globales
- num_filosofos: número de filósofos de la simulación.
- seed: semilla para la generación de números aleatorios.
- tenedores: array dinámico de mutex, uno por cada tenedor.
- timeIni: instante inicial de la simulación, en milisegundos.
int num_filosofos = 0;
int seed = 0;
int i, res; // variables de control
pthread_mutex_t *tenedores; // array de mutex
// struct timeval tiempo; // para la función "gettimeofday"
long timeIni; // indica el tiempo inicial
Función dameTiempo
Devuelve el tiempo actual en milisegundos.
long dameTiempo() // devuelve tiempo en un momento dado en milisegundos
{
struct timeval tiempo;
gettimeofday(&tiempo, NULL);
return (long)(tiempo.tv_sec * 1000 + tiempo.tv_usec / 1000);
}
Función come
El filósofo come durante un tiempo aleatorio entre 0 y 500 ms y, al terminar, libera ambos tenedores.
int come(int n)
{
long timeWait;
timeWait = rand() % 501; // número aleatorio entre 0 y 500 milisegundos
printf(" %lu: Filósofo %d come\n", (dameTiempo() - timeIni), n);
usleep(timeWait * 1000); // se multiplica por mil para pasar a microsegundos
pthread_mutex_unlock(&(tenedores)[n]);
pthread_mutex_unlock(&(tenedores)[(n + 1) % num_filosofos]);
return 0;
}
Función duerme
El filósofo duerme (piensa) durante un tiempo aleatorio entre 0 y 500 ms.
int duerme(int n)
{
long timeWait;
timeWait = rand() % 501; // número aleatorio de ms entre 0 y 500
printf(" %lu: Filósofo %d duerme\n", (dameTiempo() - timeIni), n);
usleep(timeWait * 1000);
return 0;
}
Función filosofo (hilo)
Cada hilo ejecuta el ciclo de vida de un filósofo. Se utiliza pthread_mutex_trylock para intentar tomar los tenedores sin bloquearse:
- Si obtiene el tenedor izquierdo y el derecho, come y luego duerme.
- Si solo obtiene uno, lo libera y duerme.
- Si no obtiene el primero, duerme directamente.
void *filosofo(void *arg)
{
long n = (long) arg;
while (1) {
// intentamos coger el tenedor de un lado; si está libre...
if (pthread_mutex_trylock(&(tenedores)[n]) == 0) {
// ...intentamos coger el otro tenedor; si está libre, comemos y dormimos luego
if (pthread_mutex_trylock(&(tenedores)[(n + 1) % num_filosofos]) == 0) {
come(n);
duerme(n);
} else {
// si solo hay uno, lo soltamos y dormimos
pthread_mutex_unlock(&(tenedores)[n]);
duerme(n);
}
}
// si no está disponible el primero... dormimos
else {
duerme(n);
}
}
}
Función main
La función principal realiza las siguientes tareas:
- Procesa los argumentos mediante
handle_options. - Registra el tiempo inicial de la simulación.
- Reserva memoria e inicializa un mutex por cada tenedor.
- Crea un hilo por cada filósofo.
- Espera 60 segundos antes de finalizar el programa.
int main(int argc, char *argv[])
{
int result = handle_options(argc, argv);
long i;
if (result != 0)
exit(result);
printf("Número de filósofos %d, seed %d\n", num_filosofos, seed);
// LA SEMILLA SE CONTROLA EN LAS FUNCIONES QUE GESTIONAN EL PASO DE PARÁMETROS
// fijamos el tiempo inicial en milisegundos
timeIni = dameTiempo();
// creamos los tenedores e inicializamos los mutex según el número de filósofos
tenedores = malloc(num_filosofos * sizeof(pthread_mutex_t)); // reservamos memoria para los tenedores
if (tenedores == NULL) {
perror("No hay suficiente memoria");
exit(0);
}
for (i = 0; i < num_filosofos; i++) {
res = pthread_mutex_init(&(tenedores)[i], NULL);
if (res != 0) {
perror("Error al inicializar mutex");
exit(0);
}
}
// creamos un hilo por cada filósofo
for (i = 1; i <= num_filosofos; i++) {
pthread_t tid; // para identificar los hilos
res = pthread_create(&tid, NULL, filosofo, (void *)i);
if (res < 0)
perror("creating thread");
}
sleep(60);
exit(0);
}
Observaciones
- El uso de trylock evita el interbloqueo (deadlock) clásico, ya que ningún filósofo espera indefinidamente por un tenedor.
- Los hilos se numeran desde 1 al pasar el identificador en
pthread_create, mientras que los tenedores se indexan desde 0; conviene tener en cuenta esta diferencia al ejecutar la simulación. - La función handle_options no se incluye en el fragmento original, por lo que debe estar definida en otra parte del programa.