Skip to main content

Overview

The monitor thread runs independently of philosopher threads and continuously checks for two termination conditions:
  1. Death detection: Has any philosopher exceeded time_to_die without eating?
  2. Meal completion: Have all philosophers completed their required meals?
When either condition is met, the monitor sets the global dead flag, causing all philosophers to exit their routines.

The monitor Function

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);
}
Source: monitor.c:83

Monitoring Loop

  • The monitor runs in an infinite loop
  • On each iteration, it checks both death and meal completion
  • The loop breaks when either check returns 1
  • No sleep delay between checks (continuous monitoring)
The monitor is created as the first thread before any philosopher threads, ensuring death detection is active from the simulation start.

Death Detection

check_if_dead Function

int check_if_dead(t_philo *philos)
{
    int i;

    i = 0;
    while (i < philos[0].nbr_philo)
    {
        if (philosopher_dead(&philos[i], philos[i].time_to_die))
        {
            print_message("died", &philos[i], philos[i].id);
            pthread_mutex_lock(philos[0].lock_dead);
            *philos->dead = 1;
            pthread_mutex_unlock(philos[0].lock_dead);
            return (1);
        }
        i++;
    }
    return (0);
}
Source: monitor.c:36
1

Iterate Through Philosophers

Check each philosopher sequentially from index 0 to nbr_philo - 1.
2

Check Death Condition

Call philosopher_dead() to determine if the philosopher has starved.
3

Handle Death

If death detected:
  • Print the death message
  • Lock the dead flag mutex
  • Set *philos->dead = 1
  • Unlock the mutex
  • Return 1 to end monitoring
4

Continue or Return

If no philosopher is dead, return 0 to continue monitoring.

philosopher_dead Function

int philosopher_dead(t_philo *philo, size_t time_to_die)
{
    pthread_mutex_lock(philo->lock_meal);
    if (get_current_time() - philo->last_meal_time >= time_to_die
        && philo->eating == 0)
        return (pthread_mutex_unlock(philo->lock_meal), 1);
    pthread_mutex_unlock(philo->lock_meal);
    return (0);
}
Source: monitor.c:26

Death Criteria

A philosopher is considered dead when both conditions are true:
get_current_time() - philo->last_meal_time >= time_to_die
The time since the philosopher’s last meal meets or exceeds time_to_die.
The lock_meal mutex protects access to both last_meal_time and eating, ensuring the monitor reads consistent state even while philosophers are updating these values.

Why Check eating Flag?

Without checking eating, there’s a brief window where:
  1. Philosopher acquires both forks
  2. Monitor checks time (exceeds time_to_die)
  3. Monitor declares philosopher dead
  4. Philosopher updates last_meal_time (too late)
The eating flag acts as a “grace period” indicator.
In ft_eat(), the sequence is:
philo->eating = 1;                    // Set flag first
print_message("is eating", ...);
philo->last_meal_time = get_current_time();  // Update time
ft_usleep(philo->time_to_eat);
philo->eating = 0;                    // Clear flag after eating
This ensures the monitor won’t incorrectly detect death while the philosopher is eating.

Meal Completion Tracking

check_if_all_ate Function

int check_if_all_ate(t_philo *philos)
{
    int i;
    int finished_eating;

    i = 0;
    finished_eating = 0;
    if (philos[0].max_meal == -1)
        return (0);
    while (i < philos[0].nbr_philo)
    {
        pthread_mutex_lock(philos[i].lock_meal);
        if (philos[i].meal_counter >= philos[i].max_meal)
            finished_eating++;
        pthread_mutex_unlock(philos[i].lock_meal);
        i++;
    }
    if (finished_eating == philos[0].nbr_philo)
    {
        pthread_mutex_lock(philos[0].lock_dead);
        *philos->dead = 1;
        pthread_mutex_unlock(philos[0].lock_dead);
        return (1);
    }
    return (0);
}
Source: monitor.c:56

Execution Flow

1

Check if Meal Limit Exists

if (philos[0].max_meal == -1)
    return (0);
If no meal limit was specified (5th argument omitted), return immediately without checking.
2

Count Finished Philosophers

Iterate through all philosophers, locking lock_meal to safely read meal_counter. Increment finished_eating for each philosopher who has met or exceeded their meal requirement.
3

Check Completion

if (finished_eating == philos[0].nbr_philo)
If all philosophers have finished, set the dead flag and return 1.
This check uses >= instead of == for the meal counter comparison, providing flexibility if a philosopher somehow exceeds the exact count due to timing.

Thread Safety

Mutex Usage

Protects philosopher meal state:
  • meal_counter
  • last_meal_time
  • eating flag
pthread_mutex_lock(philos[i].lock_meal);
if (philos[i].meal_counter >= philos[i].max_meal)
    finished_eating++;
pthread_mutex_unlock(philos[i].lock_meal);

The print_message Function

void    print_message(char *str, t_philo *philo, int id)
{
    size_t  time;

    pthread_mutex_lock(philo->lock_write);
    time = get_current_time() - philo->start_time;
    if (!dead_loop(philo))
        printf("%zu %d %s\n", time, id, str);
    pthread_mutex_unlock(philo->lock_write);
}
Source: monitor.c:15

Purpose

  • Serializes all console output to prevent interleaved messages
  • Formats output as: <timestamp> <philosopher_id> <message>
  • Checks dead_loop before printing to avoid messages after simulation ends
The dead_loop check inside print_message ensures that once the dead flag is set, no further status messages are printed, even if a philosopher thread hasn’t exited yet.

Monitoring Strategy

Continuous vs. Periodic Checking

This implementation uses continuous monitoring:
while (1)
    if (check_if_dead(philos) == 1 || check_if_all_ate(philos) == 1)
        break ;
Advantages:
  • Immediate death detection (minimal delay)
  • No risk of missing death between sleep intervals
  • Critical for tight time_to_die constraints
Trade-off:
  • Higher CPU usage from constant checking
  • Acceptable for this simulation’s requirements
Some implementations add a small sleep:
while (1)
{
    if (check_if_dead(philos) == 1 || check_if_all_ate(philos) == 1)
        break ;
    usleep(1000);  // Check every 1ms
}
This reduces CPU usage but adds detection latency.

Termination Flow

Monitor Thread:

  Start
    |
    v
┌─────────────────┐
│  Monitoring Loop │<──────┐
└────────┬─────────┘       │
         │                 │
         v                 │
 ┌───────────────┐          │
 │ check_if_dead │          │
 └───────┬───────┘          │
         │                 │
       Dead?               │
         │                 │
    No   │   Yes           │
    ┌────┴────┐            │
    │         v            │
    │   [Set dead=1]       │
    │   [Print death]      │
    │   [Exit loop]        │
    │                      │
    v                      │
 ┌──────────────────┐      │
 │ check_if_all_ate │      │
 └────────┬─────────┘      │
          │                │
     All ate?              │
          │                │
    No    │   Yes          │
    ──────┘   │            │
              v            │
        [Set dead=1] ──────┘
        [Exit loop]

              v
          [Return]
Once the monitor returns, the main thread joins it and then joins all philosopher threads, ensuring clean shutdown.

Build docs developers (and LLMs) love