Skip to main content

Overview

The Philosophers implementation uses a multi-threaded architecture based on POSIX threads (pthread). Each philosopher operates as an independent thread, with an additional monitor thread that oversees the simulation and checks termination conditions.

Thread Architecture

The system consists of two types of threads:
  1. Philosopher Threads: Each philosopher runs in their own thread, continuously executing the eat-sleep-think cycle
  2. Monitor Thread: A single observer thread that monitors all philosophers for death or meal completion

Thread Structure

Each philosopher thread is represented by the t_philo structure, which contains:
typedef struct s_philo
{
    pthread_t   thread;          // Thread handle
    int         id;              // Philosopher ID (1-indexed)
    int         eating;          // Currently eating flag
    int         meal_counter;    // Number of meals consumed
    long        last_meal_time;  // Timestamp of last meal
    int         *dead;           // Shared death flag
    t_mtx       *lock_dead;      // Mutex for death flag
    t_mtx       *left_fork;      // Left fork mutex
    t_mtx       *right_fork;     // Right fork mutex
    // ... other timing parameters
}       t_philo;

Thread Creation and Management

The ft_pthread_create function orchestrates the creation of all threads:
int ft_pthread_create(t_table *table, t_mtx *forks)
{
    pthread_t   observer;
    int         i;

    if (pthread_create(&observer, NULL, &monitor, table->philos) != 0)
        destroy_all("Thread creation error", table, forks);
    i = 0;
    while (i < table->philos[0].nbr_philo)
    {
        if (pthread_create(&table->philos[i].thread, NULL, &philo_routine,
                &table->philos[i]) != 0)
            destroy_all("Thread creation error", table, forks);
        i++;
    }
    i = 0;
    if (pthread_join(observer, NULL) != 0)
        destroy_all("Thread join error", table, forks);
    while (i < table->philos[0].nbr_philo)
    {
        if (pthread_join(table->philos[i].thread, NULL) != 0)
            destroy_all("Thread join error", table, forks);
        i++;
    }
    return (0);
}
The monitor thread is created first and joined first, ensuring it can observe the entire lifecycle of all philosopher threads.

The Philosopher Routine

Each philosopher thread executes the philo_routine function, which implements the core eat-sleep-think cycle:
void *philo_routine(void *pointer)
{
    t_philo *philo;

    philo = (t_philo *)pointer;
    if (philo->id % 2 == 0)
        ft_usleep(1);
    while (!dead_loop(philo))
    {
        ft_eat(philo);
        ft_sleep(philo);
        ft_think(philo);
    }
    return (pointer);
}

Key Features

  1. Thread Entry Point: Receives a pointer to the philosopher’s data structure
  2. Continuous Loop: Executes until a death condition is detected
  3. Sequential Actions: Each iteration performs eat, sleep, and think in order

Even/Odd Philosopher Delay Strategy

A critical deadlock prevention technique is implemented at the start of philo_routine:
if (philo->id % 2 == 0)
    ft_usleep(1);
This 1-millisecond delay for even-numbered philosophers (ID 2, 4, 6, etc.) staggers thread execution, preventing all philosophers from reaching for their right fork simultaneously.
Without this delay, all philosophers would start at exactly the same time and attempt to grab forks in unison, increasing the likelihood of deadlock. The staggered start creates a natural alternation in fork access patterns.

Thread Lifecycle

The complete lifecycle of philosopher threads follows this sequence:
  1. Creation (pthread_create): Main thread spawns N philosopher threads
  2. Initialization: Each thread receives its t_philo structure
  3. Delay (even IDs): Even-numbered philosophers wait 1ms
  4. Execution: Enter the eat-sleep-think loop
  5. Termination Check: After each cycle, check the shared dead flag via dead_loop()
  6. Exit: When dead flag is set, exit the loop and return
  7. Join (pthread_join): Main thread waits for all philosophers to complete

Death Flag Checking

Philosophers continuously check for termination using the dead_loop function:
int dead_loop(t_philo *philo)
{
    pthread_mutex_lock(philo->lock_dead);
    if (*philo->dead == 1)
        return (pthread_mutex_unlock(philo->lock_dead), 1);
    pthread_mutex_unlock(philo->lock_dead);
    return (0);
}
The dead flag is shared among all threads and protected by a mutex to ensure thread-safe reads. This allows the monitor thread to signal all philosopher threads to terminate gracefully.

Monitor Thread

The monitor thread runs concurrently with philosopher threads, continuously checking two conditions:
  1. Death Detection: Has any philosopher exceeded time_to_die without eating?
  2. Meal Completion: Have all philosophers reached their required meal count?
When either condition is met, the monitor sets the shared dead flag, causing all philosopher threads to exit their loops.
void *monitor(void *pointer)
{
    t_philo *philos;

    philos = (t_philo *)pointer;
    while (1)
        if (check_if_dead(philos) == 1 || check_if_all_ate(philos) == 1)
            break ;
    return (pointer);
}

Thread Joining

After the monitor thread completes (signaling termination), the main thread joins all philosopher threads in sequence:
if (pthread_join(observer, NULL) != 0)
    destroy_all("Thread join error", table, forks);
while (i < table->philos[0].nbr_philo)
{
    if (pthread_join(table->philos[i].thread, NULL) != 0)
        destroy_all("Thread join error", table, forks);
    i++;
}
The monitor thread MUST be joined first. This ensures the termination signal has been sent before attempting to join philosopher threads, preventing potential deadlocks during shutdown.

Advantages of This Model

  • True Concurrency: Each philosopher operates independently and concurrently
  • Realistic Simulation: Mirrors the actual dining philosophers problem with concurrent actors
  • Clean Separation: Monitor logic is isolated from philosopher logic
  • Graceful Termination: Shared flag allows coordinated shutdown without force-killing threads

Build docs developers (and LLMs) love