This project is a solution to the dining philosophers problem.
Five philosophers dine together at the same table. Each philosopher has his own plate at the table. There is a fork between each plate. The dish served is a kind of spaghetti which has to be eaten with two forks. Each philosopher can only alternately think and eat. Moreover, a philosopher can only eat his spaghetti when he has both a left and right fork. Thus two forks will only be available when his two nearest neighbors are thinking, not eating. After an individual philosopher finishes eating, he will put down both forks. The problem is how to design a regimen (a concurrent algorithm) such that any philosopher will not starve; i.e., each can forever continue to alternate between eating and thinking, assuming that no philosopher can know when others may want to eat or think (an issue of incomplete information).
The project is intended to teach how to use multithreading with mutexes to avoid data races.
The syntax is as follows, with four to five arguments:
./philo number_of_philosophers time_to_die time_to_eat time_to_sleep [how_many_times_philosophers should eat]
For example:
./philo 10 130 60 60 4
How well this performs will depend on your hardware. On a 3ghz i5 (on a 2019 iMac) it can handle 200 philosophers with 130 ms time_to_die, and 60 ms time_to_eat and time_to_sleep. If you have stronger hardware you may be able to run it with even more philosophers.
In the output, it is formatted as follows:
time_since_simulation_started_in_ms number_of_philosopher <message>
An example with only one cycle:
[[email protected] ~/Documents/repos/philosophers/philo]$ ./philo 4 130 60
60 1
0 2 has taken a fork
0 2 has taken a fork
0 2 is eating
0 1 is thinking
0 3 is thinking
0 4 has taken a fork
0 4 has taken a fork
0 4 is eating
60 2 is sleeping
60 1 has taken a fork
60 1 has taken a fork
60 1 is eating
60 3 has taken a fork
60 3 has taken a fork
60 3 is eating
60 4 is sleeping
The simulation will end when a single philosopher has died of starvation, or if all the philosophers have eaten the required number of meals (if the optional parameter is used for this).
- Validate parameters
- Create an array of mutexes to represent the forks
- Lock the start with a mutex
- Spawn all the philosophers as threads
- Unlock the start mutex. This ensures all philosophers wait to execute until all of them have been properly created.
- Philosophers begin to execute their eat sleep think routine.
- The main thread begins to monitor all philosophers for death and completing the simulation.
- Clean up by destroying mutexes and freeing all memory allocations.
The most important thing that I learned was how to use mutexes to synchronize all the philosopher starts, as well as how to use mutexes to protect all variables that could be accessed by more than one thread to ensure that data races do not occur.
If you would like to check on data races, you can add -fsanitize=thread as a flag to the makefile CFLAGS and LDFLAGS variables near the top of the file. This does impact performance so tests with more than 100 philosophers will likely have some performance issues with this flag on (which is why it is not enabled by default).