GithubHelp home page GithubHelp logo

maiadegraaf / philo Goto Github PK

View Code? Open in Web Editor NEW
2.0 1.0 0.0 26 KB

This project introduces the concepts of multi-threading and mutexes.

Makefile 3.75% C 96.25%
philo 42born2code codam codam-philosophers philosophers-dinner-problem philosophers42 philo-guide philo42 c

philo's Introduction

philo

philosophers

This project introduces the concepts of multi-threading and mutexes.

Table of Contents

The Challenge

This project is based on the well established Dining philosophers problem. It is set up as follows and demonstrated in the illustration above:

  • One or more philosopher sit at a round table, at the center of which is a bowl of spaghetti.
  • There are as many forks as philosophers, one between each philosopher. A fork can only be used by the philosophers sitting directly next to it.
  • The philosophers can only eat when they are holding a fork in each hand. Once they have finished eating they put down the forks.
  • The philosophers alternate between eating, sleeping, and of course thinking. They cannot do two things at once.
  • If a philosopher goes without eating for too long they will starve.
  • The philosophers cannot communicate.

The goal is ultimately to prevent the philosophers from dying, or in other words, prevent the program from deadlocking, in which progress is no longer possible.

Furthermore each philosopher will be its own process or thread, and each fork has its own mutex.

Expected Input

The program runs with the following arguments:

./philo num_of_philo time_to_die time_to_eat time_to_sleep [num_eat]
Argument Explanation
num_of_philo The number of philosophers (and forks).
time_to_die* How long it takes for the philosopher to die without eating. Measured by the time since the beginning of their last meal, or the beginning of the simulation.
time_to_eat* The length of time a philosopher spends eating.
time_to_sleep* The length of time a philosopher spends sleeping.
[optional]
num_eat
The amount of a times each philosopher must eat before the simulation ends. If not specified, the simulation only stops when a philosopher dies.

*measured in milliseconds

Expected Output

The program logs the philosophers actions to the terminal, where [time_stamp] is the time since the start of the simulation, and [x] is the philosophers number.:

[time_stamp] Philosopher [x] is thinking πŸ€”
[time_stamp] Philosopher [x] has picked up a πŸ€›fork
[time_stamp] Philosopher [x] has picked up a πŸ€›fork🀜
[time_stamp] Philosopher [x] is eating 🍝
[time_stamp] Philosopher [x] is sleeping πŸ’€
[time_stamp] Philosopher [x] is thinking πŸ€”

When a philosopher dies, the program will display the following message and end the simulation.

[time_stamp] Philosopher [x] has died πŸ’€

A Brief Guide to Threads and Mutexes

The core of this project boils down to tackling parallel execution, which is essentially creating multiple simultaneous work flows or processes. A single work flow is referred to as a thread.

A mutex (mutual exclusion), also referred to as a lock, is a mechanism used when there are multiple threads to limit access to a resource. It assures that multiple threads cannot write or read from a resource at the same time, therefore guaranteeing that one thread isn't reading a resource while another is modifying it.

Both are controlled through functions from the pthread.h library. The following are the functions we are allowed to use for this project:


pthread_create

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

Creates a new thread.

  • pthread_t *thread: the thread object, contains the thread ID.
  • pthread_attr_t *attr: attributes to assign to the thread. If NULL, the default attributes are used.
  • void *(*start_routine)(void *): the function the thread executes.
  • void *arg: the argument to pass to the above function.

pthread_detach

int pthread_detach(pthread_t thread);

Indicates to the implementation that storage for the thread can be reclaimed when that thread terminates.

  • pthread_t thread: the target thread.

pthread_join

int pthread_join(pthread_t thread, void **value_ptr);

Waits for the thread to terminate, and optionally obtain its return value.

  • pthread_t thread: the target thread.
  • void **value_ptr: if not NULL, where the value passed to pthread_exit() by the terminating thread is stored.

pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *mutex,  const pthread_mutexattr_t *attr);

Creates a new mutex.

  • pthread_mutex_t *mutex: the mutex object, contains the mutex ID.
  • const pthread_mutexattr_t *attr: attributes to assign to the mutex. If NULL, the default attributes are used.

pthread_mutex_destroy

int      pthread_mutex_destroy(pthread_mutex_t *mutex);

Frees the resources allocated to the mutex.

  • pthread_mutex_t *mutex: The target mutex.

pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

Locks the mutex. If the mutex is already locked, the calling thread will block until the mutex becomes available.

  • pthread_mutex_t *mutex: The target mutex.

pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

If the current thread holds the lock on the mutex, this function will unlock it.

  • pthread_mutex_t *mutex: The target mutex.

Return value:
All of the above functions implement return values in the same way.
If successful, the functions return zero, else an error number will be returned.

Implementation

Input and error handling

As always, the first thing the program does is parse the input and check for input errors.

The errors include:

  • The program only accepts four or five arguments.
  • All of the arguments must be able to be converted to an int.
  • There cannot be less than one philosopher.

If no errors are found, the input is then stored in a data struct t_tools. An array of t_philo's are created, each with its own pthread_t (thread) variable, and an array of pthread_mutex_t (mutexes) for each fork, both of which are the size of n , where n is the number of philosophers. Both arrays are also stored in t_tools. I choose to use an array of structs instead of a linked list as it allows for easy access to each philosopher without looping through a list, and as the number of philosophers is constant, it means the array can be malloc'ed at the beginning. The t_philo struct also stores the time of its last meal, the amount of times it has eaten, and a pointer to the t_tools struct. This means that each philosopher has access to the variables allocated by the input as well as the array of fork mutexes.

Bringing the Philosophers to Life

When pthread_create is called, each thread it creates executes a function. In my program that function is called life, which loops through each of the different phases; eat, think, and sleep. The most complicated of which is the eating phase. The philosopher first has to determine which fork lies to their left and right, and then wait until each fork becomes available by attempting to unlock the forks respective mutex. They think while they wait. Only when they have two forks they start eating. Once finished they simply unlock both mutexes. They go to sleep, after which the cycle begins again. The cycle stops either if a philosopher has died or if the philosophers have eaten the set amount of times.

To avoid a deadlock, where all philosophers grab one fork each and none of them can eat, the program starts by staggering the philosophers. Philosophers with an even id grab a fork straight away, while philosophers with an uneven id wait for a short amount of time before they can start.

Monitoring

While the parent thread waits for the rest of the threads to finish (I used pthread_join()), it monitors each philosopher. The monitoring checks each philosopher for wether the last meal time exceeds the time to die. If this is evaluated to true, the monitoring logs the death of the philosopher and ends the simulation.

Avoiding Data Races

This was by far the most challenging part of creating this program. There are two situations caused by the above implementation that could cause data races. The first is that as monitoring is constantly checking the last meal time, each philosopher is also changing this variable, causing the possibility that both threads could be trying to access the variable at the same time. The second occurs when a philosopher dies. The monitoring sets a boolean to true, which is check by each philosopher life cycle as well as the function that logs to the terminal. When the boolean is true, both functions do not execute. Again, this information is being accessed potentially simultaneously by multiple threads. To solve this problem, I implemented two more mutexes. One for the last meal time, and the other for the end boolean. Both have to helper functions to read and write, respectively.

Installation

Clone the repository:

git clone https://github.com/maiadegraaf/philo.git
cd philo
make

Some commands to try:

The philosopher should not eat and should die:

./philo 1 800 200 200.

No philosopher should die and the program should continue indefinitely:

./philo 5 800 200 200

No philosopher should die and the simulation should stop when every philosopher has eaten at least 7 times:

./philo 5 800 200 200 7

No philosopher should die and the program should continue indefinitely:

./philo 4 410 200 200

One philosopher should die:

./philo 4 310 200 100

Keep in mind that on slower computers this program might operate differently and a philosopher could die when they're not supposed to.

philo's People

Contributors

maiadegraaf avatar

Stargazers

 avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.