- How to use the Program
- Project Overview
- Divide and Conquer
- Result
- Tips for 42 students
- What I learned
Git clone the project and compile using make
.
Run the program with the following arguments:
./philo <number_of_philosophers> <time_to_die> <time_to_eat> <time_to_sleep> [number_of_times_each_philo_must_eat]
If the arguments are valid, the program will output the actions of each philosopher until one of them dies or until all of them have eaten number_of_times_each_philo_must_eat
, if specified.
The assignment is designed around Edsger Dijkstra's famous dining philosophers problem.
The subject describes the problem as follows:
- One or more philosophers sit at a round table. There is a large bowl of spaghetti in the middle of the table.
- The philosophers alternatively eat, think, or sleep. While they are eating, they are not thinking nor sleeping; while thinking, they are not eating nor sleeping; and, of course, while sleeping, they are not eating nor thinking.
- There are also forks on the table. There are as many forks as philosophers.
- Because serving and eating spaghetti with only one fork is very inconvenient, a philosopher takes their right and their left forks to eat, one in each hand.
- When a philosopher has finished eating, they put their forks back on the table and start sleeping. Once awake, they start thinking again. The simulation stops when a philosopher dies of starvation.
- Every philosopher needs to eat and should never starve.
- Philosophers don’t speak with each other.
- Philosophers don’t know if another philosopher is about to die.
- No need to say that philosophers should avoid dying!
The program takes 5 to 6 arguments:
number_of_philosophers
: The number of philosophers and also the number of forks.time_to_die
(in milliseconds): If a philosopher didn’t start eatingtime_to_die
milliseconds since the beginning of their last meal or the beginning of the simulation, they die.time_to_eat
(in milliseconds): The time it takes for a philosopher to eat. During that time, they will need to hold two forks.time_to_sleep
(in milliseconds): The time a philosopher will spend sleeping.number_of_times_each_philosopher_must_eat
(optional argument): If all philosophers have eaten at leastnumber_of_times_each_philosopher_must_eat
times, the simulation stops. If not specified, the simulation stops when a philosopher dies.
The programs outputs a message each time a philosopher takes an action, which is formatted as follows:
- [ms since the beginning of the simulation] [philosopher number] has taken a fork
- [ms since the beginning of the simulation] [philosopher number] is eating
- [ms since the beginning of the simulation] [philosopher number] is sleeping
- [ms since the beginning of the simulation] [philosopher number] is thinking
- [ms since the beginning of the simulation] [philosopher number] died
I got started with identifying the concepts that were new to me.
1. Research
- The dining philosophers problem by Edsger Dijkstra
- concept of threads and mutexes
- Linux Programmer Manual:
- hread_create(3)
- pthread_join(3)
- pthread_mutex_init/lock/unlock(3)
- gettimeofday(2)
- research on timeval type + how dates work
- datarace
- deadlock
- difference between routine and process
2. Code Structure
- creating a Makefile that doesn't relink
- creating a header file
3. Implementation Parsing:
- take the arguments and turn them into int with
atoi
- check if the numbers are positive and not bigger than
INT_MAX
, otherwise throw an error - check if the number of philosophers given is under 200
- set up gettime
Execution:
- creation of threads: In the mandatory part, each philosopher is a thread and each fork is protected by a mutex. There is exactly one fork between each philosopher and a philosopher must take the forks to his immediate left and right in order to eat.
- Use of mutexes (first lock, then read or modify the shared resources, then unlock): protect the shared resources since the philosophers can’t communicate with each other, also protect the
printf
functions to prevent message from overlapping. - Attribution of forks: the mutex corresponding to a fork is locked as long as a philosopher uses it. Forks are taken one after the other.
- Routine: The elements of this program which form the routine are obviously eat, sleep and think. When the program starts, the philosophers with even numbers check if their forks are available, one after the other and eat. After they ate
time_to_eat
ms, they’re going to sleep. Afterwards, they’re going to think until they can eat again. The others do the same, with one particularity, the odd ones wait a bit before starting. This make sure that all the even philosophers starts and ensure the synchronization. At each step, I implemented a check to see if there is a dead philosopher. The execution will stop if it the case. Since the philosophers can die during an action, I implemented asuspend thread function
that uses multiple shortusleep
functions in a loop.
Here are examples of commands and their execution with my program:
./philo 4 310 200 100
:
![0 1 has taken a fork](https://private-user-images.githubusercontent.com/114035440/251554950-5456cd89-a3e1-4eb6-83c1-fa804da29d28.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUwMjc2ODYsIm5iZiI6MTcyNTAyNzM4NiwicGF0aCI6Ii8xMTQwMzU0NDAvMjUxNTU0OTUwLTU0NTZjZDg5LWEzZTEtNGViNi04M2MxLWZhODA0ZGEyOWQyOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODMwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgzMFQxNDE2MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0xNmZiMzc4ZGM1YTc4NzE3NmYzMjQ4ZTQyY2E3MjRjNzA3OWFjYjYxMDExOGFhZGY1ZDA0NGQ2ODU1M2M4MzI4JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.8i3B8Tjz9RVnhdGN700cB7e1T97b9YL0fLx0zu2MYQg)
![Screen Shot 2023-07-04 at 10 41 17 AM](https://private-user-images.githubusercontent.com/114035440/251554996-d9533c7b-f3c8-40d5-bdd5-7796034f69c8.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUwMjc2ODYsIm5iZiI6MTcyNTAyNzM4NiwicGF0aCI6Ii8xMTQwMzU0NDAvMjUxNTU0OTk2LWQ5NTMzYzdiLWYzYzgtNDBkNS1iZGQ1LTc3OTYwMzRmNjljOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODMwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgzMFQxNDE2MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT05OTBiMTAwZmQzMGY0MDY1YjlmMTQxZTk5YTcwM2E1ZjA4OTRjMzcwMWIyZGFlNTE5OWQzZmYyMTUwMGJiNzk5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.zIJmg3uw28OhqA2WYMQIpoyuXb2DCAX-rViXRY2tt84)
./philo 3 400 100 100 3
:
![Screen Shot 2023-07-06 at 3 28 38 PM](https://private-user-images.githubusercontent.com/114035440/251555354-0758788e-5acd-430c-96e6-6b00a6403065.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUwMjc2ODYsIm5iZiI6MTcyNTAyNzM4NiwicGF0aCI6Ii8xMTQwMzU0NDAvMjUxNTU1MzU0LTA3NTg3ODhlLTVhY2QtNDMwYy05NmU2LTZiMDBhNjQwMzA2NS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODMwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgzMFQxNDE2MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT03MmEzYWJiN2VmOGFiZjI2OGE3ODBiZmU4NWY3ZGI2ZGE0ODNjMDllNDZiOTRmNTg2Njk4NTAxZDhhMWFiOTY0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9._YiqbfPS0HIHVg0cfEtTET4cjzNQ_IV0UvoWNQvZg_Y)
./philo 5 800 200 200
:
![Screen Shot 2023-07-06 at 3 29 48 PM](https://private-user-images.githubusercontent.com/114035440/251555604-229cbfa0-2c59-42c6-89d9-0cf42aa0c801.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUwMjc2ODYsIm5iZiI6MTcyNTAyNzM4NiwicGF0aCI6Ii8xMTQwMzU0NDAvMjUxNTU1NjA0LTIyOWNiZmEwLTJjNTktNDJjNi04OWQ5LTBjZjQyYWEwYzgwMS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODMwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgzMFQxNDE2MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT00NTMyZTgzZDI1NDc2Mjk4OGE2MzE0ZmY5N2Q2ZDU1ODVjYWM0OTZlNzYzNzQ0NDQzMTZlOWU3YzIzZDgwNzM4JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.GQ_mP1ua0dbggoBW3metY4g65LGfOlg22gOuCGx0hkg)
./philo 20 400 300 100
:
![Screen Shot 2023-07-06 at 3 30 31 PM](https://private-user-images.githubusercontent.com/114035440/251555774-e5fdd66a-893d-40a5-a40c-91d28088b083.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUwMjc2ODYsIm5iZiI6MTcyNTAyNzM4NiwicGF0aCI6Ii8xMTQwMzU0NDAvMjUxNTU1Nzc0LWU1ZmRkNjZhLTg5M2QtNDBhNS1hNDBjLTkxZDI4MDg4YjA4My5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODMwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgzMFQxNDE2MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1mYjcwZTA1NzZkOTBhYTVkNTg2OTUyZGI3NDA3ZmM4ZjlmMzA5NDdjMGEwOWUwOWU1YmJlNjNmNDgyNTQyNjg5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.Uj0aE1D5j2Rl0k7P2Ivel1VOYA5hkUHc8utMH5nodAU)
For the visualization, I used the philosophers-visualizer by Nafuka11.
- you can add
-fsanitize=thread
to your flags in your makefile to check if there are data races, however, I recommend to only use this flag during the evaluation since it causes a high CPU usage - an excellent source to understand threads and mutexes: codevault
- debugging: make sure to unlock your mutexes after locking them
- prevent leaks: For a thread to free their resources they must always return their routine function otherwise you will leak their allocated memory. To do that, you can either detach or join it. If you join a thread you're making sure the main thread waits for the created thread to return its routine and consequently free its resources.
- error detection possible with valgrind --tool=drd ./philo
- attribution of forks: I first tried to let the philosophers check if forks are available and take them at the same time, but this led to problems when there was an odd number of philosophers
- The subject states: “Replace timestamp_in_ms with the current timestamp in milliseconds and X with the philosopher number.” As I understand it, it refers to the epoch time. So, normally, we should print the Unix epoch time, but it’s easier to do debugging and the evaluations if we use the ms elapsed since the beginning of the simulation, i.e. ```timestamp – time start of simulation’’’
While working on this project, I deepened my understanding of multithreading and synchronization concepts. Managing concurrent access to shared resources was completely new to me, but easier than I thought at the beginning. I learned how to use mutexes to ensure thread safety and prevent race conditions. Furthermore, I delved into the intricacies of deadlock detection and avoidance. Lastly, I discovered the concept of epoch time which made me feel like a entered a bit more in the progamming world. I really liked the project, even though it was kind of long in the end, final learning: spaghetti comes to those who wait!