philosophers est un projet concurrence / synchronisation en C basé sur le problème classique des “Dining Philosophers”. L’objectif est de gérer plusieurs threads/processus qui partagent des ressources (les fourchettes) sans tomber dans les pièges habituels : deadlocks, race conditions et incohérences de logs.

L’objectif est double :

  • Comprendre et maîtriser la programmation concurrente (threads/processus) et la synchronisation.
  • Construire une simulation robuste : règles de temps respectées, arrêt propre, affichage cohérent.

Principe

Le programme simule n philosophes assis autour d’une table, qui alternent :

  1. prendre des fourchettes (ressources partagées),
  2. manger pendant time_to_eat,
  3. dormir pendant time_to_sleep,
  4. penser.

Contrainte critique : si un philosophe ne mange pas dans time_to_die, il meurt et la simulation s’arrête (selon les règles du sujet).

Selon la version :

  • mandatory : threads + mutex (fourchettes et affichage),
  • bonus : processus + sémaphores (IPC).

Points importants

  • Synchronisation :

  • protection des fourchettes (mutex/semaphore),

  • protection des logs (éviter des messages mélangés),
  • protection des états partagés (dernier repas, compteur de repas, flag stop…).
  • Éviter les deadlocks :

  • stratégie d’ordre de prise des fourchettes (pair/impair ou hiérarchie),

  • éventuellement limitation via un “waiter” / sémaphore global.
  • Monitoring :

  • un thread/processus surveille les timestamps pour détecter une mort,

  • arrêt propre de tous les workers dès qu’une condition de fin est atteinte.
  • Précision temporelle :

  • timestamps en ms,

  • usleep/boucles contrôlées pour limiter la dérive tout en évitant la surconsommation CPU.

Contraintes respectées

  • Langage : C
  • Arguments : number_of_philosophers, time_to_die, time_to_eat, time_to_sleep, [number_of_times_each_philosopher_must_eat]
  • Primitives de synchronisation (mutex / sémaphores selon version)
  • Zéro leak + arrêt propre (threads join / cleanup des ressources)
  • Logs formatés (timestamp + id) sans interleaving

Ce que ça m’a apporté

philosophers m’a appris à raisonner correctement sur la concurrence : identifier les sections critiques, choisir une stratégie anti-deadlock, protéger l’état partagé, et rendre une simulation stable même sous stress (timings serrés, beaucoup de philosophes). C’est un vrai déclic pour comprendre le scheduling et les effets de bord en multi-thread.

Bonus (Bientôt)

  • Version processus + sémaphores (IPC) au lieu de threads/mutex.
  • Stratégies alternatives (waiter global, optimisation de l’attente, logs améliorés).

Lancement

./philo number_of_philosophers time_to_die time_to_eat time_to_sleep [number_of_times_each_philosopher_must_eat]

Signification

  • number_of_philosophers : nombre de philosophes (et fourchettes)
  • time_to_die : temps max sans manger avant de mourir (ms)
  • time_to_eat : durée pour manger (ms)
  • time_to_sleep : durée pour dormir (ms)
  • optionnel : nombre de repas requis pour arrêter la simulation (si tous ont atteint ce nombre)

Événements et sorties

Chaque action est loggée avec :

  • un timestamp (ms depuis le début)
  • l’id du philosophe
  • l’action (prendre une fourchette / manger / dormir / penser / mort)

Conditions de fin

  • Si un philosophe dépasse time_to_die sans manger → mort → arrêt.
  • Si l’option “nombre de repas” est fournie et que tous l’atteignent → arrêt propre.

Stratégie (mandatory)

  • Threads : 1 thread par philosophe
  • Mutex :

  • 1 mutex par fourchette

  • 1 mutex pour l’affichage/log (éviter l’interleaving)
  • mutex pour l’état partagé (stop flag, last_meal, meals_count)

Éviter les deadlocks

Approches classiques :

  • Ordre de prise différent selon l’id (pair/impair)
  • Hiérarchie stricte (toujours prendre la plus petite fourchette id en premier)
  • “Waiter” global (limiter le nombre de philosophes pouvant tenter de manger) (souvent bonus/alternative)

Monitoring

  • Un composant de surveillance (thread dédié ou boucle contrôlée) vérifie régulièrement :

  • now - last_meal > time_to_die → déclenche la mort

  • À la fin : signaler l’arrêt et join tous les threads.

Précision et CPU

  • Utiliser une fonction get_time_ms() stable (ex. gettimeofday)
  • Utiliser un smart_sleep() (petits usleep) pour éviter :

  • trop de CPU

  • dérives temporelles importantes