Problema de los filósofos comensales en C: implementación con hilos y mutex

Informática5,55 KB

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:

  1. Procesa los argumentos mediante handle_options.
  2. Registra el tiempo inicial de la simulación.
  3. Reserva memoria e inicializa un mutex por cada tenedor.
  4. Crea un hilo por cada filósofo.
  5. 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.

Descargar

PDF Word HTML