diff --git a/README.md b/README.md index 2ed5976..71418a3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ # dining-philosophers -A commented example of solving the dining philosophers multithreading problem. \ No newline at end of file +A commented example of solving the dining philosophers multithreading problem. + + +## Description of the dining philosophers problem. + +The dinner philosophers problem consists of a round table where +N number of philosophers are seated, every philosopher has a fork +on each side that they are supposed to share with their neighbours +to eat a spaghetti that can only eated by the usage of two +forks. + +The problem consists on achieving the table to be able to continuously +eat without getting stuck in a deadlock without the chance of philosophers to +talk between themselves. + +## Description of the solution. + +For the solution I inspired on the Dijkstra's solution but using the OOP nature +of C++ to make it more readable and near to the natural language. + +The code is commented in its tricky parts in order to make the solution easier to understand. + +[https://en.wikipedia.org/wiki/Dining_philosophers_problem](https://en.wikipedia.org/wiki/Dining_philosophers_problem) diff --git a/include/philosopher.hpp b/include/philosopher.hpp index bad154e..27608d6 100644 --- a/include/philosopher.hpp +++ b/include/philosopher.hpp @@ -17,47 +17,81 @@ typedef std::shared_ptr ListOfPhilosophersPtr; extern std::mutex changingForkState; extern std::mutex printing; +/** + * \brief A class representing a single philosopher having his dinner. + * + * Philosophers are seated in a round table. + * In order of being able to eat a philosopher should be able to take + * the two forks on his sides so no other of its neighbour philosophers + * should be eating at this time. + */ class Philosopher { private: + //! The position of the philosopher in the table. int numberOfPhilosopher; + //! What the philosopher is doing. PhilosopherState state{PhilosopherState::THINKING}; + //! The vector of philosophers in the table. ListOfPhilosophersPtr philosophers; + //! The function to set the state of the philosopher. void setState(PhilosopherState state); + //! Method to guess the numberOfPhilosopher of the left neighbour of this philosopher. size_t leftPhilosopherNumber(void); + //! Method to guess the numberOfPhilosopher of the right neighbour of this philosopher. size_t rightPhilosopherNumber(void); + //! Retrieve the left neighbour. PhilosopherPtr leftPhilosopher(void); + //! Retrieve the right neighbour. PhilosopherPtr rightPhilosopher(void); + //! Makes the philosopher think for a while. void - think(); + think(void); + //! The philosopher waits until the forks of his neighbours are free and then takes the two forks. void - takeForks(); + takeForks(void); + + //! The philosopher eats the spaghetti with the two forks. void - eat(); + eat(void); + //! The philosopher puts the forks in the table to be used by his neighbours. void - putForks(); + putForks(void); public: + //! Retrieves the philosopher state. PhilosopherState - getState(); + getState(void); + + /** + * \brief Philosopher constructor + * @param philosophers The vector of in the table philosophers. + * @param numberOfPhilosopher The position of this philosopher in the table. + */ Philosopher(ListOfPhilosophersPtr philosophers, int numberOfPhilosopher); + + //! Whenever this philosopher was the two forks currently. std::binary_semaphore hasBothForks{0}; - void startThread(); - void test(); + + //! Starts the philosopher dinner. + void startThread(void); + + //! Test if the philosopher should be able to take both forks currently. + void test(void); }; diff --git a/src/main.cpp b/src/main.cpp index 16aeba6..0d41f22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,33 +14,56 @@ findNumberOfPhilosophersInParams(const int argc, char **argv, int *const numberO int main(int argc, char **argv) { - ListOfPhilosophersPtr philosophers; - philosophers = ListOfPhilosophersPtr(new ListOfPhilosophers()); + // std::shared_ptr> + ListOfPhilosophersPtr philosophers(new ListOfPhilosophers()); + // Default value for the number of dining philosophers. int numberOfPhilosophers = 5; + // Attempts to find a tailored value provided by the user for numberOfPhilosophers. findNumberOfPhilosophersInParams(argc, argv, &numberOfPhilosophers); printf("%d philosophers to create.\n", numberOfPhilosophers); + // Pushes a number of philosophers equal to numberOfPhilosophers to the table. for (int i = 0; i < numberOfPhilosophers; i++) { printf("Creating philosopher: %d.\n", i); philosophers->push_back(PhilosopherPtr(new Philosopher(philosophers, i))); } + // If the jthreads go out of scope is equal than joining, which is undersirable on the + // insides of a for loop if we want they to execute in parallel. std::vector threads; for (unsigned long int i = 0; i < philosophers->size(); i++) { - std::shared_ptr numberOfPhilosopher(new unsigned long(i)); + // A pitfall would be to use here i directly since it would be incremented before + // it is used by jthread, so a pointer is needed. + std::shared_ptr numberOfPhilosopher(new unsigned long()); + *numberOfPhilosopher = i; threads.push_back(std::jthread([philosophers, numberOfPhilosopher] { + // This starts the dinner for the philosopher *numberOfPhilosopher. + // While philosophers are yet not dinning the state for them is thinking + // anyway, so they have to problem with the neighbours dining already. (*philosophers)[*numberOfPhilosopher]->startThread(); } )); } } +/* + * Checks if the parameter list is greater or equal than 2 and takes the second parameter.- + * The first if ignoring the current executable name.- And with this parameter attempts to + * get a number to be used as the number of philosophers in the table. + * + * In case of failure, numberOfPhilosophers is not changed, else is set to the integer value + * of the parameter. + */ void findNumberOfPhilosophersInParams(const int argc, char **argv, int *const numberOfPhilosophers) { - if (argc >= 2) { - const char *numberOfPhilosophersAsString = argv[1]; - try { - *numberOfPhilosophers = std::stoi(numberOfPhilosophersAsString, NULL, 10); - } catch (std::exception &ex) { - printf("Unable to read the number of philosophers, continuing\n"); - } + // The user did not gave us enough arguments, early return. + if (argc < 2) { + return; + } + // numberOfPhilosophersAsString is now equal to the supposed number the user gave us. + const char *numberOfPhilosophersAsString = argv[1]; + try { + // Converts param to number, can throw an exception. + *numberOfPhilosophers = std::stoi(numberOfPhilosophersAsString, NULL, 10); + } catch (std::exception &ex) { + printf("The number of philosophers is not a valid number, continuing\n"); } } diff --git a/src/philosopher.cpp b/src/philosopher.cpp index 47c210c..76d5760 100644 --- a/src/philosopher.cpp +++ b/src/philosopher.cpp @@ -8,56 +8,79 @@ Philosopher::setState(PhilosopherState state) { } size_t Philosopher::leftPhilosopherNumber(void) { + // Circular implementation of the table to the left. + // (0 - 1 + 9) % 9 = 8 + // (1 - 1 + 9) % 9 = 0 return (numberOfPhilosopher - 1 + philosophers->size()) % philosophers->size(); } size_t Philosopher::rightPhilosopherNumber(void) { + // Circular implementation of the table to the right. + // (8 + 1) % 9 = 0 + // (7 + 1) % 9 = 8 return (numberOfPhilosopher + 1) % philosophers->size(); } + PhilosopherPtr Philosopher::leftPhilosopher(void) { return (*philosophers)[leftPhilosopherNumber()]; } + PhilosopherPtr Philosopher::rightPhilosopher(void) { return (*philosophers)[rightPhilosopherNumber()]; } + void Philosopher::think() { + // The number of milliseconds thinking. size_t duration = random(100, 1000); { + // Holds the lock for printing. std::lock_guard lk{printing}; std::cout << "Philosopher " << numberOfPhilosopher << " is thinking for " << duration << "." << std::endl; } + // Sleeps. std::this_thread::sleep_for(std::chrono::milliseconds(duration)); } void Philosopher::takeForks() { { + // Holds the lock for state manipulation and read. std::lock_guard lk{changingForkState}; + // Sets the philosopher to be hungry. setState(PhilosopherState::HUNGRY); { + // Holds the lock for printing. std::lock_guard lk{printing}; std::cout << "Philosopher " << numberOfPhilosopher << " is hungry." << std::endl; } + // Attempts to release the binary semaphore for forks locking if still up. test(); } + // Attempts to adquire the forks lock. hasBothForks.acquire(); } void Philosopher::eat() { + // Sets the time eating. size_t duration = random(100, 1000); { + // Holds the lock for printing. std::lock_guard lk{printing}; std::cout << "Philosopher " << numberOfPhilosopher << " is eating." << std::endl; } + // Sleeps the time the philosopher last to eat. std::this_thread::sleep_for(std::chrono::milliseconds(duration)); } void Philosopher::putForks() { + // Holds the lock for state manipulation and read. std::lock_guard lk{changingForkState}; - state = PhilosopherState::THINKING; + // Set the philosopher in thinking state. + setState(PhilosopherState::THINKING); + // Attempts to free the lock of neighbours if they are locked because of this philosopher. leftPhilosopher()->test(); rightPhilosopher()->test(); } @@ -80,15 +103,20 @@ Philosopher::startThread() { std::cout << "Philosopher " << numberOfPhilosopher << " has sit in the table." << std::endl; } while (true) { + // The philosopher starts thinking. think(); + // Then gets hungry and attempts to eat. takeForks(); + // When the two forks are available the philosopher eats. eat(); + // Then the philosopher puts the two forks on the table to be used by the philosopher's neighbours. putForks(); } } void Philosopher::test() { + // This is self-documenting. if (state == PhilosopherState::HUNGRY && leftPhilosopher()->getState() != PhilosopherState::EATING && rightPhilosopher()->getState() != PhilosopherState::EATING) {