├── img └── os-dining-philosophers-problem.gif ├── delay_o_meter.py ├── Makefile ├── src ├── routine_actions.c ├── threads.c ├── main.c ├── utils.c ├── init.c ├── philo.h └── monitor.c └── README.md /img/os-dining-philosophers-problem.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeRuina/philosophers/HEAD/img/os-dining-philosophers-problem.gif -------------------------------------------------------------------------------- /delay_o_meter.py: -------------------------------------------------------------------------------- 1 | import time 2 | from statistics import mean 3 | 4 | 5 | def measure() -> float: 6 | """ 7 | Measure how much of delay this machine will add on average 8 | while performing a 200 millisecond sleep. 9 | 10 | Returns a float, indicating a delay in milliseconds 11 | """ 12 | start = time.perf_counter_ns() 13 | time.sleep(200 / 1000) 14 | end = time.perf_counter_ns() 15 | delay = ((end - start) / 1000000) - 200 16 | return delay 17 | 18 | 19 | if __name__ == "__main__": 20 | avgs = [] 21 | 22 | print("Measuring", end="", flush=True) 23 | for i in range(0, 20): 24 | print(".", end="", flush=True) 25 | avgs.append(measure()) 26 | print("\n") 27 | 28 | print( 29 | f"For 200ms of usleep this machine adds {mean(avgs):.3f}ms of delay on average" 30 | ) 31 | print(f"Peak delay: {max(avgs):.3f}ms") 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # **************************************************************************** # 2 | # # 3 | # ::: :::::::: # 4 | # Makefile :+: :+: :+: # 5 | # +:+ +:+ +:+ # 6 | # By: druina +#+ +:+ +#+ # 7 | # +#+#+#+#+#+ +#+ # 8 | # Created: 2023/06/26 14:19:21 by druina #+# #+# # 9 | # Updated: 2023/08/18 15:17:39 by druina ### ########.fr # 10 | # # 11 | # **************************************************************************** # 12 | 13 | NAME = philo 14 | 15 | SRC = main.c utils.c init.c threads.c monitor.c routine_actions.c 16 | 17 | MANPATH = $(addprefix ./src/, $(SRC)) 18 | 19 | FLAGS = -Wall -Wextra -Werror -O3 -pthread 20 | 21 | HEADER = ./src/philo.h 22 | 23 | # SANITIZER = -fsanitize=thread 24 | 25 | .PHONY: all clean fclean re debug 26 | 27 | all: $(NAME) 28 | 29 | $(NAME): $(MANPATH) $(HEADER) 30 | @cc $(FLAGS) -o $(NAME) $(MANPATH) $(SANITIZER) 31 | 32 | clean: 33 | @rm -f $(NAME) 34 | 35 | fclean: clean 36 | @rm -f $(NAME) 37 | 38 | re: fclean all 39 | 40 | debug: FLAGS += -g 41 | debug: re 42 | 43 | delay: 44 | python3 delay_o_meter.py 45 | -------------------------------------------------------------------------------- /src/routine_actions.c: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* routine_actions.c :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: druina +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2023/08/17 08:48:15 by druina #+# #+# */ 9 | /* Updated: 2023/08/17 08:48:44 by druina ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | #include "philo.h" 14 | 15 | // Think routine funtion 16 | 17 | void think(t_philo *philo) 18 | { 19 | print_message("is thinking", philo, philo->id); 20 | } 21 | 22 | // Dream routine funtion 23 | 24 | void dream(t_philo *philo) 25 | { 26 | print_message("is sleeping", philo, philo->id); 27 | ft_usleep(philo->time_to_sleep); 28 | } 29 | 30 | // Eat routine funtion 31 | 32 | void eat(t_philo *philo) 33 | { 34 | pthread_mutex_lock(philo->r_fork); 35 | print_message("has taken a fork", philo, philo->id); 36 | if (philo->num_of_philos == 1) 37 | { 38 | ft_usleep(philo->time_to_die); 39 | pthread_mutex_unlock(philo->r_fork); 40 | return ; 41 | } 42 | pthread_mutex_lock(philo->l_fork); 43 | print_message("has taken a fork", philo, philo->id); 44 | philo->eating = 1; 45 | print_message("is eating", philo, philo->id); 46 | pthread_mutex_lock(philo->meal_lock); 47 | philo->last_meal = get_current_time(); 48 | philo->meals_eaten++; 49 | pthread_mutex_unlock(philo->meal_lock); 50 | ft_usleep(philo->time_to_eat); 51 | philo->eating = 0; 52 | pthread_mutex_unlock(philo->l_fork); 53 | pthread_mutex_unlock(philo->r_fork); 54 | } 55 | -------------------------------------------------------------------------------- /src/threads.c: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* threads.c :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: druina +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2023/08/11 14:01:57 by druina #+# #+# */ 9 | /* Updated: 2023/08/17 08:48:42 by druina ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | #include "philo.h" 14 | 15 | // Checks if the value of dead_flag changed 16 | 17 | int dead_loop(t_philo *philo) 18 | { 19 | pthread_mutex_lock(philo->dead_lock); 20 | if (*philo->dead == 1) 21 | return (pthread_mutex_unlock(philo->dead_lock), 1); 22 | pthread_mutex_unlock(philo->dead_lock); 23 | return (0); 24 | } 25 | 26 | // Thread routine 27 | 28 | void *philo_routine(void *pointer) 29 | { 30 | t_philo *philo; 31 | 32 | philo = (t_philo *)pointer; 33 | if (philo->id % 2 == 0) 34 | ft_usleep(1); 35 | while (!dead_loop(philo)) 36 | { 37 | eat(philo); 38 | dream(philo); 39 | think(philo); 40 | } 41 | return (pointer); 42 | } 43 | 44 | // Creates all the threads 45 | 46 | int thread_create(t_program *program, pthread_mutex_t *forks) 47 | { 48 | pthread_t observer; 49 | int i; 50 | 51 | if (pthread_create(&observer, NULL, &monitor, program->philos) != 0) 52 | destory_all("Thread creation error", program, forks); 53 | i = 0; 54 | while (i < program->philos[0].num_of_philos) 55 | { 56 | if (pthread_create(&program->philos[i].thread, NULL, &philo_routine, 57 | &program->philos[i]) != 0) 58 | destory_all("Thread creation error", program, forks); 59 | i++; 60 | } 61 | i = 0; 62 | if (pthread_join(observer, NULL) != 0) 63 | destory_all("Thread join error", program, forks); 64 | while (i < program->philos[0].num_of_philos) 65 | { 66 | if (pthread_join(program->philos[i].thread, NULL) != 0) 67 | destory_all("Thread join error", program, forks); 68 | i++; 69 | } 70 | return (0); 71 | } 72 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* main.c :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: druina +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2023/06/26 14:23:28 by druina #+# #+# */ 9 | /* Updated: 2023/08/16 16:30:43 by druina ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | #include "philo.h" 14 | 15 | // Checks that the input is only numbers 16 | 17 | int check_arg_content(char *arg) 18 | { 19 | int i; 20 | 21 | i = 0; 22 | while (arg[i] != '\0') 23 | { 24 | if (arg[i] < '0' || arg[i] > '9') 25 | return (1); 26 | i++; 27 | } 28 | return (0); 29 | } 30 | 31 | // Checks if the program input is correct 32 | 33 | int check_valid_args(char **argv) 34 | { 35 | if (ft_atoi(argv[1]) > PHILO_MAX || ft_atoi(argv[1]) <= 0 36 | || check_arg_content(argv[1]) == 1) 37 | return (write(2, "Invalid philosophers number\n", 29), 1); 38 | if (ft_atoi(argv[2]) <= 0 || check_arg_content(argv[2]) == 1) 39 | return (write(2, "Invalid time to die\n", 21), 1); 40 | if (ft_atoi(argv[3]) <= 0 || check_arg_content(argv[3]) == 1) 41 | return (write(2, "Invalid time to eat\n", 21), 1); 42 | if (ft_atoi(argv[4]) <= 0 || check_arg_content(argv[4]) == 1) 43 | return (write(2, "Invalid time to sleep\n", 23), 1); 44 | if (argv[5] && (ft_atoi(argv[5]) < 0 || check_arg_content(argv[5]) == 1)) 45 | return (write(2, "Invalid number of times each philosopher must eat\n", 46 | 51), 1); 47 | return (0); 48 | } 49 | // Main function 50 | 51 | int main(int argc, char **argv) 52 | { 53 | t_program program; 54 | t_philo philos[PHILO_MAX]; 55 | pthread_mutex_t forks[PHILO_MAX]; 56 | 57 | if (argc != 5 && argc != 6) 58 | return (write(2, "Wrong argument count\n", 22), 1); 59 | if (check_valid_args(argv) == 1) 60 | return (1); 61 | init_program(&program, philos); 62 | init_forks(forks, ft_atoi(argv[1])); 63 | init_philos(philos, &program, forks, argv); 64 | thread_create(&program, forks); 65 | destory_all(NULL, &program, forks); 66 | return (0); 67 | } 68 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* utils.c :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: druina +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2023/08/16 09:17:55 by druina #+# #+# */ 9 | /* Updated: 2023/08/16 16:31:28 by druina ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | #include "philo.h" 14 | 15 | // Checks the len of the string 16 | 17 | int ft_strlen(char *str) 18 | { 19 | int i; 20 | 21 | if (str == NULL) 22 | return (0); 23 | i = 0; 24 | while (str[i] != '\0') 25 | i++; 26 | return (i); 27 | } 28 | // Own version of atoi 29 | 30 | int ft_atoi(char *str) 31 | { 32 | unsigned long long nb; 33 | int sign; 34 | int i; 35 | 36 | nb = 0; 37 | sign = 1; 38 | i = 0; 39 | while (str[i] == ' ' || str[i] == '\t' || str[i] == '\n' || str[i] == '\v' 40 | || str[i] == '\f' || str[i] == '\r') 41 | i++; 42 | if (str[i] == '-') 43 | sign = -1; 44 | if (str[i] == '-' || str[i] == '+') 45 | i++; 46 | while (str[i] >= '0' && str[i] <= '9') 47 | { 48 | nb = nb * 10 + (str[i] - '0'); 49 | i++; 50 | } 51 | return (sign * nb); 52 | } 53 | // Destroys all the mutexes 54 | 55 | void destory_all(char *str, t_program *program, pthread_mutex_t *forks) 56 | { 57 | int i; 58 | 59 | i = 0; 60 | if (str) 61 | { 62 | write(2, str, ft_strlen(str)); 63 | write(2, "\n", 1); 64 | } 65 | pthread_mutex_destroy(&program->write_lock); 66 | pthread_mutex_destroy(&program->meal_lock); 67 | pthread_mutex_destroy(&program->dead_lock); 68 | while (i < program->philos[0].num_of_philos) 69 | { 70 | pthread_mutex_destroy(&forks[i]); 71 | i++; 72 | } 73 | } 74 | 75 | // Improved version of sleep function 76 | 77 | int ft_usleep(size_t milliseconds) 78 | { 79 | size_t start; 80 | 81 | start = get_current_time(); 82 | while ((get_current_time() - start) < milliseconds) 83 | usleep(500); 84 | return (0); 85 | } 86 | 87 | // Gets the current time in milliseconds 88 | 89 | size_t get_current_time(void) 90 | { 91 | struct timeval time; 92 | 93 | if (gettimeofday(&time, NULL) == -1) 94 | write(2, "gettimeofday() error\n", 22); 95 | return (time.tv_sec * 1000 + time.tv_usec / 1000); 96 | } 97 | -------------------------------------------------------------------------------- /src/init.c: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* init.c :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: druina +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2023/08/09 12:05:40 by druina #+# #+# */ 9 | /* Updated: 2023/08/17 08:59:27 by druina ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | #include "philo.h" 14 | 15 | // Initializing the input from user 16 | 17 | void init_input(t_philo *philo, char **argv) 18 | { 19 | philo->time_to_die = ft_atoi(argv[2]); 20 | philo->time_to_eat = ft_atoi(argv[3]); 21 | philo->time_to_sleep = ft_atoi(argv[4]); 22 | philo->num_of_philos = ft_atoi(argv[1]); 23 | if (argv[5]) 24 | philo->num_times_to_eat = ft_atoi(argv[5]); 25 | else 26 | philo->num_times_to_eat = -1; 27 | } 28 | 29 | // Initializing the philosophers 30 | 31 | void init_philos(t_philo *philos, t_program *program, pthread_mutex_t *forks, 32 | char **argv) 33 | { 34 | int i; 35 | 36 | i = 0; 37 | while (i < ft_atoi(argv[1])) 38 | { 39 | philos[i].id = i + 1; 40 | philos[i].eating = 0; 41 | philos[i].meals_eaten = 0; 42 | init_input(&philos[i], argv); 43 | philos[i].start_time = get_current_time(); 44 | philos[i].last_meal = get_current_time(); 45 | philos[i].write_lock = &program->write_lock; 46 | philos[i].dead_lock = &program->dead_lock; 47 | philos[i].meal_lock = &program->meal_lock; 48 | philos[i].dead = &program->dead_flag; 49 | philos[i].l_fork = &forks[i]; 50 | if (i == 0) 51 | philos[i].r_fork = &forks[philos[i].num_of_philos - 1]; 52 | else 53 | philos[i].r_fork = &forks[i - 1]; 54 | i++; 55 | } 56 | } 57 | 58 | // Initializing the forks mutexes 59 | 60 | void init_forks(pthread_mutex_t *forks, int philo_num) 61 | { 62 | int i; 63 | 64 | i = 0; 65 | while (i < philo_num) 66 | { 67 | pthread_mutex_init(&forks[i], NULL); 68 | i++; 69 | } 70 | } 71 | 72 | // Initializing the program structure 73 | 74 | void init_program(t_program *program, t_philo *philos) 75 | { 76 | program->dead_flag = 0; 77 | program->philos = philos; 78 | pthread_mutex_init(&program->write_lock, NULL); 79 | pthread_mutex_init(&program->dead_lock, NULL); 80 | pthread_mutex_init(&program->meal_lock, NULL); 81 | } 82 | -------------------------------------------------------------------------------- /src/philo.h: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* philo.h :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: druina +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2023/06/26 14:20:06 by druina #+# #+# */ 9 | /* Updated: 2023/08/17 08:59:39 by druina ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | #ifndef PHILO_H 14 | # define PHILO_H 15 | # include 16 | # include 17 | # include 18 | # include 19 | # include 20 | 21 | # define PHILO_MAX 300 22 | 23 | typedef struct s_philo 24 | { 25 | pthread_t thread; 26 | int id; 27 | int eating; 28 | int meals_eaten; 29 | size_t last_meal; 30 | size_t time_to_die; 31 | size_t time_to_eat; 32 | size_t time_to_sleep; 33 | size_t start_time; 34 | int num_of_philos; 35 | int num_times_to_eat; 36 | int *dead; 37 | pthread_mutex_t *r_fork; 38 | pthread_mutex_t *l_fork; 39 | pthread_mutex_t *write_lock; 40 | pthread_mutex_t *dead_lock; 41 | pthread_mutex_t *meal_lock; 42 | } t_philo; 43 | typedef struct s_program 44 | { 45 | int dead_flag; 46 | pthread_mutex_t dead_lock; 47 | pthread_mutex_t meal_lock; 48 | pthread_mutex_t write_lock; 49 | t_philo *philos; 50 | } t_program; 51 | 52 | // Main functions 53 | int check_arg_content(char *arg); 54 | int check_valid_args(char **argv); 55 | void destory_all(char *str, t_program *program, 56 | pthread_mutex_t *forks); 57 | 58 | // Initialization 59 | void init_program(t_program *program, t_philo *philos); 60 | void init_forks(pthread_mutex_t *forks, int philo_num); 61 | void init_philos(t_philo *philos, t_program *program, 62 | pthread_mutex_t *forks, char **argv); 63 | void init_input(t_philo *philo, char **argv); 64 | 65 | // Threads 66 | int thread_create(t_program *program, pthread_mutex_t *forks); 67 | void *monitor(void *pointer); 68 | void *philo_routine(void *pointer); 69 | 70 | // Actions 71 | void eat(t_philo *philo); 72 | void dream(t_philo *philo); 73 | void think(t_philo *philo); 74 | 75 | // Monitor utils 76 | int dead_loop(t_philo *philo); 77 | int check_if_all_ate(t_philo *philos); 78 | int check_if_dead(t_philo *philos); 79 | int philosopher_dead(t_philo *philo, size_t time_to_die); 80 | 81 | // Utils 82 | int ft_atoi(char *str); 83 | int ft_usleep(size_t microseconds); 84 | int ft_strlen(char *str); 85 | void print_message(char *str, t_philo *philo, int id); 86 | size_t get_current_time(void); 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /src/monitor.c: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* monitor.c :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: druina +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2023/08/11 22:22:47 by druina #+# #+# */ 9 | /* Updated: 2023/08/16 17:27:17 by druina ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | #include "philo.h" 14 | 15 | // Print message funtion 16 | 17 | void print_message(char *str, t_philo *philo, int id) 18 | { 19 | size_t time; 20 | 21 | pthread_mutex_lock(philo->write_lock); 22 | time = get_current_time() - philo->start_time; 23 | if (!dead_loop(philo)) 24 | printf("%zu %d %s\n", time, id, str); 25 | pthread_mutex_unlock(philo->write_lock); 26 | } 27 | 28 | // Checks if the philosopher is dead 29 | 30 | int philosopher_dead(t_philo *philo, size_t time_to_die) 31 | { 32 | pthread_mutex_lock(philo->meal_lock); 33 | if (get_current_time() - philo->last_meal >= time_to_die 34 | && philo->eating == 0) 35 | return (pthread_mutex_unlock(philo->meal_lock), 1); 36 | pthread_mutex_unlock(philo->meal_lock); 37 | return (0); 38 | } 39 | 40 | // Check if any philo died 41 | 42 | int check_if_dead(t_philo *philos) 43 | { 44 | int i; 45 | 46 | i = 0; 47 | while (i < philos[0].num_of_philos) 48 | { 49 | if (philosopher_dead(&philos[i], philos[i].time_to_die)) 50 | { 51 | print_message("died", &philos[i], philos[i].id); 52 | pthread_mutex_lock(philos[0].dead_lock); 53 | *philos->dead = 1; 54 | pthread_mutex_unlock(philos[0].dead_lock); 55 | return (1); 56 | } 57 | i++; 58 | } 59 | return (0); 60 | } 61 | 62 | // Checks if all the philos ate the num_of_meals 63 | 64 | int check_if_all_ate(t_philo *philos) 65 | { 66 | int i; 67 | int finished_eating; 68 | 69 | i = 0; 70 | finished_eating = 0; 71 | if (philos[0].num_times_to_eat == -1) 72 | return (0); 73 | while (i < philos[0].num_of_philos) 74 | { 75 | pthread_mutex_lock(philos[i].meal_lock); 76 | if (philos[i].meals_eaten >= philos[i].num_times_to_eat) 77 | finished_eating++; 78 | pthread_mutex_unlock(philos[i].meal_lock); 79 | i++; 80 | } 81 | if (finished_eating == philos[0].num_of_philos) 82 | { 83 | pthread_mutex_lock(philos[0].dead_lock); 84 | *philos->dead = 1; 85 | pthread_mutex_unlock(philos[0].dead_lock); 86 | return (1); 87 | } 88 | return (0); 89 | } 90 | 91 | // Monitor thread routine 92 | 93 | void *monitor(void *pointer) 94 | { 95 | t_philo *philos; 96 | 97 | philos = (t_philo *)pointer; 98 | while (1) 99 | if (check_if_dead(philos) == 1 || check_if_all_ate(philos) == 1) 100 | break ; 101 | return (pointer); 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Philosophers 42 Explained / Walkthrough 3 | #### The famous "Dining Philosophers Problem" walkthrough - 42 cursus project 4 | 5 | 6 | ## The Dining Philosophers Problem 7 | [The dining philosophers problem](https://en.wikipedia.org/wiki/Dining_philosophers_problem) is a famous problem in computer science used to illustrate common issues in [concurrent programming](https://en.wikipedia.org/wiki/Concurrency_(computer_science)). The problem was originally formulated in 1965 by [Edsger Dijkstra](https://en.wikipedia.org/wiki/Edsger_W._Dijkstra), and is stated as follows: 8 | 9 | X amount of philosophers sit at a round table with bowls of food. 10 | Forks are placed in front of each philosopher. 11 | There are as many forks as philosophers. 12 | All day the philosophers take turns eating, sleeping, and thinking. 13 | A philosopher must have two forks in order to eat, and each fork 14 | may only be used by one philosopher at a time. At any time a 15 | philosopher can pick up or set down a fork, 16 | but cannot start eating until picking up both forks. 17 | The philosophers alternatively eat, sleep, or think. 18 | While they are eating, they are not thinking nor sleeping, 19 | while thinking, they are not eating nor sleeping, 20 | and, of course, while sleeping, they are not eating nor thinking. 21 | Let me start by explaining the general idea. First of all, we have to imagine a round table, X num of philosophers sitting around it and each of them brings a fork and places it in front of them. At this point we know that a philosopher can do three things: eat, sleep, or think, but in order to eat he has to pick two forks (the one in front of him and another one to his right or to his left, in my solution he picks the one to his right, both work - different implementation). Let's use a picture to have a more concrete idea of what we are talking about: 22 | 23 | ![The dining philosopers problem](img/os-dining-philosophers-problem.gif) 24 | 25 | Let's say there are 5 philosophers sitting at the table. Philosopher 1 wants to eat, so he picks the fork in front of him and the one to his right (the one in front of philosopher 5), at this point, we notice that philosopher 2 can't eat nor does philospher 5, since philosopher 1 picked the fork in front of him and in front of philisopher 5. this might seem a little obvious but keep in mind this situation because the main problem of this project is how to organize the eating action of the philosophers. 26 | Probably the first solution that came to your mind is to simply make the odd and even philos eat separately, well we are not going to do that, it's too hard coded and we would lose the meaning of the project, philos have to organize by themselves. We will be using [threads](https://en.wikipedia.org/wiki/Thread_(computing)) and implement a [multithreading solution](https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)). 27 | I'm attaching the [project subject](https://cdn.intra.42.fr/pdf/pdf/96340/en.subject.pdf) as well so you could understand completely what is needed. 28 | 29 | In order to understand the solution you'll needs to understand the concept of threads first, here are some good videos I recommend you watch: 30 | 31 | * [General introduction to threads](https://www.youtube.com/watch?v=LOfGJcVnvAk) 32 | 33 | * [Introduction to threads with code examples](https://www.youtube.com/watch?v=ldJ8WGZVXZk) 34 | 35 | * [Short introduction to threads (pthreads)](https://www.youtube.com/watch?v=d9s_d28yJq0&list=PLfqABt5AS4FmuQf70psXrsMLEDQXNkLq2) ** 36 | 37 | ** Code Vault covers all the knowledge you need for this project/problem in this playlist 38 | 39 | ## Data Races (Race Conditions) - What Are They? 40 | Data races are a common problem in multithreaded programming. Data races occur when multiple tasks or threads access a shared resource without sufficient protections, leading to undefined or unpredictable behavior. 41 | * two or more threads concurrently accessing a location of memory 42 | * one of them is a write 43 | * one of them is unsynchronized 44 | 45 | In simpler words a race condition can happen when 2 or more threads are trying to access and modify the same variable at the same time, it can lead to an error in the final value of the variable, it doesn't mean it will for sure happen though. For an example let's think of a function that deposits the amount you insert to your bank account, If we use multithreading and use 2 threads and want to deposit 300 using the first thread and 200 using the second you will think our bank account will have a total of 500, but that's not particularly the case, let's see it in code: 46 | ``` c 47 | #include 48 | #include 49 | #include 50 | 51 | // the initial balance is 0 52 | int balance = 0; 53 | 54 | // write the new balance (after as simulated 1/4 second delay) 55 | void write_balance(int new_balance) 56 | { 57 | usleep(250000); 58 | balance = new_balance; 59 | } 60 | 61 | // returns the balance (after a simulated 1/4 seond delay) 62 | int read_balance() 63 | { 64 | usleep(250000); 65 | return balance; 66 | } 67 | 68 | // carry out a deposit 69 | void* deposit(void *amount) 70 | { 71 | // retrieve the bank balance 72 | int account_balance = read_balance(); 73 | 74 | // make the update locally 75 | account_balance += *((int *) amount); 76 | 77 | // write the new bank balance 78 | write_balance(account_balance); 79 | 80 | return NULL; 81 | } 82 | 83 | int main() 84 | { 85 | // output the balance before the deposits 86 | int before = read_balance(); 87 | printf("Before: %d\n", before); 88 | 89 | // we'll create two threads to conduct a deposit using the deposit function 90 | pthread_t thread1; 91 | pthread_t thread2; 92 | 93 | // the deposit amounts... the correct total afterwards should be 500 94 | int deposit1 = 300; 95 | int deposit2 = 200; 96 | 97 | // create threads to run the deposit function with these deposit amounts 98 | pthread_create(&thread1, NULL, deposit, (void*) &deposit1); 99 | pthread_create(&thread2, NULL, deposit, (void*) &deposit2); 100 | 101 | // join the threads 102 | pthread_join(thread1, NULL); 103 | pthread_join(thread2, NULL); 104 | 105 | // output the balance after the deposits 106 | int after = read_balance(); 107 | printf("After: %d\n", after); 108 | 109 | return 0; 110 | } 111 | ``` 112 | You would think if you run this code that the balance will be 500 but the output we get is actually 200, now, why is that? Here is a visualization of the above program's execution: 113 | ``` 114 | Thread #1 Thread #2 Bank Balance 115 | 116 | Read Balance <----------------------------------- 0 117 | balance = 0 118 | Read Balance <------------- 0 119 | balance = 0 120 | 121 | Deposit +300 122 | balance = 300 123 | Deposit +200 124 | balance = 200 125 | 126 | Write Balance ----------------------------------> 300 127 | balance = 300 128 | Write Balance ------------> 200 129 | balance = 200 130 | ``` 131 | When the two deposit functions run at the same time, they both read the balance which is 0, they both deposit the amount inserted and change the balance variable **locally**, but when they write it to the variable itself, they overwrite each other. thread #1 writes 300 first but thread #2 writes as well and changes the variable to 200. How can we solve this? easily we just need to attach a lock, let me introduce you to mutex. 132 | 133 | * [What are Race Conditions?](https://www.youtube.com/watch?v=FY9livorrJI&list=PLfqABt5AS4FmuQf70psXrsMLEDQXNkLq2&index=3) - Great explanation down to the Assembly level 134 | * [Race Conditions Explained With An Example](https://www.youtube.com/watch?v=K1aoimUYTK8) 135 | 136 | ## Mutex 137 | Now that we know what is a race condition let's see what is the solution. Imagine a lock that protects a block of code and it can be only executed by the lock owner until he unlocks the lock. Taking the previous example we can avoid the overwrite by adding a lock in the deposit function. if thread #1 reaches the lock thread #2 will just have to wait until thread #1 is done executing the code and reaches the unlock, only then thread #2 will enter and execute himself. 138 | ```c 139 | #include 140 | #include 141 | #include 142 | 143 | // the initial balance is 0 144 | int balance = 0; 145 | 146 | // write the new balance (after as simulated 1/4 second delay) 147 | void write_balance(int new_balance) 148 | { 149 | usleep(250000); 150 | balance = new_balance; 151 | } 152 | 153 | // returns the balance (after a simulated 1/4 seond delay) 154 | int read_balance() 155 | { 156 | usleep(250000); 157 | return balance; 158 | } 159 | 160 | // carry out a deposit 161 | void* deposit(void *amount) 162 | { 163 | // lock the mutex 164 | pthread_mutex_lock(&mutex); 165 | 166 | // retrieve the bank balance 167 | int account_balance = read_balance(); 168 | 169 | // make the update locally 170 | account_balance += *((int *) amount); 171 | 172 | // write the new bank balance 173 | write_balance(account_balance); 174 | 175 | // unlock to make the critical section available to other threads 176 | pthread_mutex_unlock(&mutex); 177 | 178 | return NULL; 179 | } 180 | 181 | int main() 182 | { 183 | // mutex variable 184 | pthread_mutex_t mutex; 185 | 186 | // output the balance before the deposits 187 | int before = read_balance(); 188 | printf("Before: %d\n", before); 189 | 190 | // we'll create two threads to conduct a deposit using the deposit function 191 | pthread_t thread1; 192 | pthread_t thread2; 193 | 194 | // initialize the mutex 195 | pthread_mutex_init(&mutex, NULL); 196 | 197 | // the deposit amounts... the correct total afterwards should be 500 198 | int deposit1 = 300; 199 | int deposit2 = 200; 200 | 201 | // create threads to run the deposit function with these deposit amounts 202 | pthread_create(&thread1, NULL, deposit, (void*) &deposit1); 203 | pthread_create(&thread2, NULL, deposit, (void*) &deposit2); 204 | 205 | // join the threads 206 | pthread_join(thread1, NULL); 207 | pthread_join(thread2, NULL); 208 | 209 | // destroy the mutex 210 | pthread_mutex_destroy(&mutex); 211 | 212 | // output the balance after the deposits 213 | int after = read_balance(); 214 | printf("After: %d\n", after); 215 | 216 | return 0; 217 | } 218 | ``` 219 | You have surely noticed that we initialize and destroy the mutex, and you have to do that every time you want to use a mutex (destroy it after you finished using it) otherwise it won't work. 220 | 221 | Here is another visualization with the locks: 222 | ``` 223 | Thread #1 Thread #2 Bank Balance 224 | 225 | ** LOCK ** 226 | 227 | WAIT @ LOCK Read Balance <------------- 0 228 | | balance = 0 229 | | 230 | | Deposit +200 231 | | balance = 200 232 | | 233 | | Write Balance ------------> 200 234 | | balance = 200 235 | | 236 | LOCK FREE ** UNLOCK ** 237 | 238 | ** LOCK ** 239 | 240 | Read Balance <----------------------------------- 200 241 | balance = 0 242 | 243 | Deposit +300 244 | balance = 500 245 | 246 | Write Balance ----------------------------------> 500 247 | balance = 500 248 | 249 | ** UNLOCK ** 250 | ``` 251 | 252 | * [What is a mutex in C? (pthread_mutex)](https://www.youtube.com/watch?v=oq29KUy29iQ&list=PLfqABt5AS4FmuQf70psXrsMLEDQXNkLq2&index=4) 253 | * [Mutex Introduction (pthreads)](https://www.youtube.com/watch?v=raLCgPK-Igc&t=424s) 254 | 255 | ## Step By Step Guide/Walkthrough 256 | 257 | ### First Step: Checking Valid Input 258 | The first thing we need to do before we even start initializing anything is to check the program input. The program will receive 4 or 5 arguments so the first thing should be to throw an error if we receive more or less. Let's analyze the input we will receive: 5 800 200 200 7 259 | * 5 - The number of philosophers 260 | * 800 - The time a philosopher will die if he doesn't eat 261 | * 200 - The time it takes a philosopher to eat 262 | * 200 - The time it takes a philosopher to sleep 263 | * 7 - Number of times all the philosophers need to eat before terminating the program ** 264 | 265 | ** optional argument 266 | 267 | Basically, all we need to do is to check that the input contains only numbers, they should all be bigger than 0 except the number of meals each philo needs to eat (edge case). In the evaluation form, it says we should not test with more than 200 philos so you can set the limit not to be more than 200. 268 | 269 | ### Second Step: Structures 270 | In order for you to understand the way I approached and solved this project I'll share with you the structures I made. Because each philosopher needs to be a thread and all the data needs to pass to the routine functions, structures are the best option. I created 2 structures, The program structure which holds all of the philosophers (in an array), 3 mutex, and one dead_flag, and the philo structure where we have all of the general data, 3 mutex pointers that point to the mutex in the program structure, 2 mutex pointers for the forks, and on dead pointer which points to the dead flag in the program structure. 271 | 272 | ``` c 273 | typedef struct s_philo 274 | { 275 | pthread_t thread; 276 | int id; 277 | int eating; 278 | int meals_eaten; 279 | size_t last_meal; 280 | size_t time_to_die; 281 | size_t time_to_eat; 282 | size_t time_to_sleep; 283 | size_t start_time; 284 | int num_of_philos; 285 | int num_times_to_eat; 286 | int *dead; 287 | pthread_mutex_t *r_fork; 288 | pthread_mutex_t *l_fork; 289 | pthread_mutex_t *write_lock; 290 | pthread_mutex_t *dead_lock; 291 | pthread_mutex_t *meal_lock; 292 | } t_philo; 293 | 294 | typedef struct s_program 295 | { 296 | int dead_flag; 297 | pthread_mutex_t dead_lock; 298 | pthread_mutex_t meal_lock; 299 | pthread_mutex_t write_lock; 300 | t_philo *philos; 301 | } t_program; 302 | 303 | ``` 304 | ### Third Step: Initialization 305 | Because we know the maximum amount of philosophers our program can be tested with (200) and I wanted to avoid dealing with leaks, freeing, and allocating, and mainly because I wanted the performance to be faster I decided to keep all the memory on the stack and not on the heap by initializing a philo structure array, a mutex array for the forks and the program structure all in the main. From there I initialize the program variables, initialize all the mutexes for the mutex fork array, and lastly the philosophers - input variables and point the pointers to all the mutexes and the dead_flag. 306 | 307 | ### Fourth Step: Thread Creation, Philo Routine, And Monitor 308 | Now we need to create the threads and join them. We will create as many threads as philosophers we have, each philo needs to be a thread and we will create an extra thread (I called it observer) which will monitor everything. Each philo thread will run the philo routine function and the observer will run the monitor function. 309 | 310 | #### Philo Routine() 311 | The routine will be the function executed over and over by the philos, Basically, I created a loop that will break as soon as the dead flag is 1, in other words as soon as a philo is dead. Remember: 312 | 313 | The philosophers alternatively eat, sleep, or think. 314 | While they are eating, they are not thinking nor sleeping, 315 | while thinking, they are not eating nor sleeping, 316 | and, of course, while sleeping, they are not eating nor thinking. 317 | 318 | So in our loop, they will eat, sleep and think. Let's start with the easiest one when they think we just need to print a message "X is thinking" (X is the philo number), When they sleep we need to make them sleep the length of the input inserted by the user using our ft_usleep (described in the bottom of this page) and then print the message "X is sleeping". Now to the eating part, We will lock the right fork first using pthread_mutex_lock and print the message, and do the same with the left fork. Then he will eat using ft_usleep again and only then he will drop the forks by unlocking the locks, before that we change some variables that give our monitor indications but that's the general idea. 319 | 320 | #### Monitor() 321 | This thread will be running and monitoring the whole program, it has 2 checks in it that run infinitely until a philo dies or they all ate the number of meals they need to (last input argument). Basically, we will check that the time a philo needs to die didn't surpass the last meal he had and that he is not concurrently eating. If he indeed died we change the dead flag to 1 and that will break the loop in all of the threads. The other check is to see if all the philos finished eating the amount of meals they need to, and if they did we will again change the dead flag to one and break the threads loop. 322 | 323 | ### Fifth Step: Destroying All The Mutexes 324 | The last step is to Destroy all the mutexes you initialized, otherwise, they won't work. In this step, we will free all the data we allocated if we chose to allocate it (we didn't). 325 | 326 | ## Utils Functions - **Improtant** 327 | 328 | #### Sleep Function Delay 329 | 330 | Different machines perform the sleep function with different accuracy. You can check your machine by running the script in this repo. This can help make sure other stuff running on the computer doesn't interfere with Philosopher's timings. For this reason, as well we created the ft_usleep function. 331 | Run the script by downloading it and: 332 | ```bash 333 | python3 delay_o_meter.py 334 | ``` 335 | or by running my Makefile with the command: 336 | ```bash 337 | make delay 338 | ``` 339 | 340 | #### ft_usleep 341 | ``` c 342 | // Improved version of sleep function 343 | int ft_usleep(size_t milliseconds) 344 | { 345 | size_t start; 346 | 347 | start = get_current_time(); 348 | while ((get_current_time() - start) < milliseconds) 349 | usleep(500); 350 | return (0); 351 | } 352 | ``` 353 | 354 | #### get_current_time 355 | ``` c 356 | // Gets the current time in milliseconds 357 | 358 | size_t get_current_time(void) 359 | { 360 | struct timeval time; 361 | 362 | if (gettimeofday(&time, NULL) == -1) 363 | write(2, "gettimeofday() error\n", 22); 364 | return (time.tv_sec * 1000 + time.tv_usec / 1000); 365 | } 366 | ``` 367 | ## Usage ## 368 | 369 | ```bash 370 | # Clone this project 371 | $ git clone https://github.com/DeRuina/philosophers.git 372 | 373 | # Access 374 | $ cd philosophers 375 | 376 | # Compile the program 377 | $ make 378 | 379 | # Run the project 380 | $ ./philo 381 | 382 | ``` 383 | ** Don't forget to insert the arguments 384 | 385 | ## Author 386 | 387 | - [@DeRuina](https://github.com/DeRuina) 388 | --------------------------------------------------------------------------------