Skip to content

alcarril/Philosofers

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

22 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Philosophers

Multi-threaded simulation solving the Dining Philosophers problem via POSIX synchronization metrics
42 C GNU Makefile

philosophers


πŸ’‘ Overview

This is my 9th project at 42 Network, a Dining Philosophers project that uses threads to parallelize operations and relies on synchronization mechanisms to avoid data races, such as mutexes.

The objective is to keep the philosophers alive for the maximum amount of time possible.


✨ Key Features

Concurrency & Thread Management

  • Thread Lifecycle & Handlers: Dynamic creation of pthread workers coupled with dedicated execution routines (routines/handlers) per philosopher.
  • Clean Resource Sanitation: Guaranteeing a 100% leak-free and safe exit by systematically joining threads (pthread_join) and destroying mutex blocks when termination criteria are met.
  • Shared Memory Architecture: Exploring how threads, unlike processes, share the exact same address space, highlighting both the speed of memory access and its inherent risks.

Resource Synchronization & Safety

  • Mutex Orchestration: Hardening critical sections using pthread_mutex locks to safely gate access to forks and status variables.
  • Race Condition & Data Race Elimination: Engineering foolproof state checks to prevent multiple threads from modifying the same variable concurrently.
  • Deadlock Prevention: Implementing precise resource acquisition logic (e.g., asymmetric fork-picking protocols) to ensure the system never locks itself into an unresolvable stale state.

Performance Optimization

  • CPU Resource Preservation: Strategic deployment of granular usleep intervals. This prevents waiting threads from entering tight "busy-waiting" loops, keeping CPU consumption at zero while waiting for a mutex lock.
  • Task Parallelism: Harnessing true multi-core capabilities by executing independent philosopher lifecycles simultaneously, maximizing execution throughput.


πŸš€ Getting Started

Requirements

sudo apt install gcc       # C compiler
sudo apt install make      # Build tool for the Makefile
sudo apt install libc6-dev # C standard library headers

Build

git clone https://github.com/alcarril/Philosofers.git # Download the repository
cd Philosophers/philo                                 # Enter the project directory
make                                                  # Compile the program

Run

./philo [number_of_philosophers] [time_to_die] [time_to_eat] [time_to_sleep] [max_meals_per_philosopher]
./philo 5 800 200 200


🎬 Demo

philo_demo.gif

⚠️ Hardware & Environment Advisory: This project is specifically tailored and optimized to meet the strict evaluation constraints within the official 42 school campus machines. When dealing with multi-threaded execution loops and microsecond-level timing precision, hardware variance across different CPU architectures, core layouts, and thread scheduling policies is critical. Running this code on external host environments or different processing hardware may directly alter the simulation's behavior and overall timing accuracy.



πŸ“œ The Problem & Constraints

The simulation implements the classic Dining Philosophers problem under strict resource constraints. $N$ philosophers run independent thread loops cycling through eating, sleeping, and thinking.

🍽️ Core Simulation Rules

  • The Resource Deficit: One fork per philosopher. A philosopher can only eat after locking both adjacent forks (left $i$, right $(i+1) \pmod N$), and releases them after eating.
  • Absolute Isolation: No communication or coordination between neighbors; each thread follows its own loop.
  • The Starvation Clock: If a philosopher fails to start eating within time_to_die ms from the start or last meal, the simulation stops.
  • Shared Resource Contention: Neighboring forks are shared, so all access must be synchronized to avoid data races and deadlocks.

⏱️ Required Precision, Latency & Benchmarks

The scheduler must meet strict timing guarantees under tight time, load, and API constraints:

  • Scale & Threshold Limits: Support 200 concurrent threads and remain stable when time_to_die, time_to_eat, time_to_sleep are as low as 60 ms.
  • Microsecond-Level Tolerance: Timing lag is punished; in 2-philosopher tests, death notices delayed by >10 ms are invalid.
  • Edge-Case Accuracy: Behavior must match the boundary cases below:
    • Running ./philo 4 410 200 200 must keep all threads alive indefinitely.
    • Running ./philo 4 310 200 200 must cleanly capture exactly one starvation event and terminate.
    • Running ./philo 5 800 200 200 7 must cleanly stop the entire simulation the exact moment every philosopher has eaten at least 7 times.

πŸ› οΈ Authorized Functions: The project can only use the set of authorized functions listed in the Notion page below. πŸ”— Notion Guide: Funciones Autorizadas


🎯 My Solution

To meet the constraints without fatalities, the project uses a lean multi-threaded architecture plus a dedicated monitor.

πŸ—οΈβ€‹ Program Architecture

  • One Thread per Philosopher: Each philosopher runs inside an independent thread loop, executing a continuous state routine (eat βž” sleep βž” think).
  • One Mutex per Fork: Every fork is mapped to a unique pthread_mutex_t to guarantee that no two threads can hold the same resource concurrently.
  • Asymmetric Fork-Picking Protocol: To completely eliminate deadlocks, odd-numbered philosophers grab their left fork first, while even-numbered philosophers grab their right fork first. This completely breaks the circular wait condition.

β€‹πŸ‘οΈβ€‹ Dedicated Monitor Subsystem

The simulation runs a Dedicated Monitor Thread (not a philosopher) that orchestrates global state:

  • Starvation Tracking: It constantly reads an is_dead state flag modified by the philosophers. If starvation is detected, it instantly flags a global stop_program variable.
  • Meal Threshold Control: It checks if the optional minimum meal count has been reached by all threads to trigger a synchronized halt.
  • Clean Termination: Philosophers continuously read the stop_program flag during their routines, allowing them to stop execution instantly and clear resources cleanly.

πŸ“ Memory Isolation & Read/Write Strategy

Instead of locking every state transition, the design separates reads and writes to reduce contention:

  • Philosopher Threads (Leaves): They exclusively write to their own dedicated slot in the is_dead[n] array upon starvation, and recurrently read the global stop_program flag.
  • Monitor Thread (Control Branch): It acts as the central hub. It reads the is_dead array and, upon detecting a death or meal completion, safely locks the terminal write-mutex, dumps the exit information, and writes true to the global stop_program flag.
  • The Isolated Case (1 Philo): If only one philosopher is present, the system bypasses fork validation entirely since a single thread can never acquire two forks, routing execution directly to the starvation timer.

Design Iteration: The Logging Thread Experiment

An alternative design offloaded all prints to a Logging Thread via a queue to avoid a shared STDOUT mutex in worker threads.

In practice, microsecond-level ordering constraints from the 42 subject exposed queue and context-switch latency, so the design reverted to direct, mutex-locked STDOUT writes for deterministic timestamps.


Multi-threaded execution tree mapping read/write data isolation between worker threads and the tracking system.
Philosophers Control Tree


ℹ️ Core Concepts and Resources

For a deep, granular breakdown of the operating system mechanics underpinning this project, you can access my personal Notion documentation (written in Spanish). Use the links below to explore each architectural concept:


πŸ‘¨β€πŸ’»β€‹ Author

Alejandro Carrillo (alcarril) - GitHub

Releases

No releases published

Packages

 
 
 

Contributors