Adding comments.
This commit is contained in:
parent
1908d8f29d
commit
e578422eab
24
README.md
24
README.md
@ -1,3 +1,25 @@
|
||||
# dining-philosophers
|
||||
|
||||
A commented example of solving the dining philosophers multithreading problem.
|
||||
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)
|
||||
|
@ -17,47 +17,81 @@ typedef std::shared_ptr<ListOfPhilosophers> 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);
|
||||
};
|
||||
|
||||
|
43
src/main.cpp
43
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<std::vector<std::shared_ptr<Philosopher>>
|
||||
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<std::jthread> threads;
|
||||
for (unsigned long int i = 0; i < philosophers->size(); i++) {
|
||||
std::shared_ptr<unsigned long> 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<unsigned long> 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<std::mutex> 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<std::mutex> lk{changingForkState};
|
||||
// Sets the philosopher to be hungry.
|
||||
setState(PhilosopherState::HUNGRY);
|
||||
{
|
||||
// Holds the lock for printing.
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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) {
|
||||
|
Loading…
Reference in New Issue
Block a user