0

I am trying to write a program solving 5 dining philosophers problem. I am on the beginning, so for now I want every philosopher to just think, eat, without any synchronization, looking at forks etc. This is what I wrote:

#pragma once
#include <atomic>
#include <chrono>
#include <mutex>
#include <random>
#include <thread>
#include <vector>

#include <fork.hpp>

class dining_philosophers {
public:
    // std::vector<philosopher> philosophers;
    std::array<fork, 5> forks;
    // ui u;
    dining_philosophers();
    std::atomic<bool> ready{false};
};

dining_philosophers::dining_philosophers() {}

class philosopher {
public:
    dining_philosophers &table;
    // ui &u;
    fork &left_fork;
    fork &right_fork;
    std::mt19937 rng{std::random_device{}()};
    int state = -1;
    int progress = 0;
    int id;
    std::thread t;
    philosopher();
    philosopher(int _id, dining_philosophers &table_ref, fork l, fork r)
        : id(_id), left_fork(l), right_fork(r), table(table_ref),
          t(&philosopher::live, this) {}
    void live();
    void eat();
    void think();
    void wait_for_forks();
    void release_forks();
};

void philosopher::live() {
    while (!table.ready) {
        std::this_thread::yield();
    }
    while (true) {
        think();
        // wait_for_forks();
        eat();
        // release_forks();
    }
}

void philosopher::think() {
    state = 0;
    int part = std::uniform_int_distribution<int>(15, 25)(rng);
    int thinkingTime = part * 200; // in miliseconds
    for (auto i = 0; i < part; i++) {
        double p = (double)i / (double)part;
        progress = p * 100;
        // std::thread t(&ui::update_state, &u, id, "thinking", progress);
        // u.update_state(id, "thinking", progress);
        // t.join();
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

void philosopher::eat() {
    state = 1;
    int part = std::uniform_int_distribution<int>(15, 25)(rng);
    int thinkingTime = part * 200; // in miliseconds
    for (auto i = 0; i < part; i++) {
        double p = (double)i / (double)part;
        progress = p * 100;
        // std::thread t(&ui::update_state, &u, id, "thinking", progress);
        // u.update_state(id, "thinking", progress);
        // t.join();
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

class fork is just empty, I wrote it to use it later.

Now I need to print some information about what's going on using ncurses. I thought about making it like this: I have a global vector of philosophers. Every 200ms my other thread using ui::update() function checks for philosophers members, like id, state and progress and prints it out. I wrote something like this:

#include <iostream>
#include <ncurses.h>
#include <thread>
#include <vector>

#include <dining_philosophers.hpp>

std::vector<philosopher> philosophers;

class ui {
private:
    int row;
    int col;
    std::mutex m;

public:
    ui();
    ~ui();
    void print_initial_state();
    void update_state(int id, const char *state, int progress);
    void update();
};

ui::ui() {
    initscr();
    // noecho();
    start_color();
    getmaxyx(stdscr, col, row);
}

ui::~ui() { endwin(); }

void ui::update() {
    int x = 10;
    int y = 10;
    while (true) {
        for (auto &phil : philosophers) {
            int id = phil.id;
            int state = phil.state;
            int progress = phil.progress;
            move(y + id - 1, 0);
            clrtoeol();
            move(y + id - 1, x);
            printw("Philosopher %d is %d, progress: %d%%", id, state, progress);
            refresh();
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

int main() {
    dining_philosophers table;
    ui u;
    // std::vector<philosopher> philosophers;
    for (auto i = 0; i < 4; i++) {
        philosophers.push_back(
            philosopher(i + 1, table, table.forks[i], table.forks[i + 1]));
    }
    philosophers.push_back(
        philosopher(5, table, table.forks[4], table.forks[0]));
    // std::thread t{[&]() {}};
    std::this_thread::sleep_for(std::chrono::seconds(1));
    table.ready = true;
    std::thread t1(&ui::update, &u);
    // std::this_thread::sleep_for(std::chrono::seconds(5));
    // t.join();
    t1.join();
    for (auto &p : philosophers) {
        p.t.join();
    }
}

I know I don't have a proper thread closing, for now I use Ctrl+C. The problem is that ncurses for five philosophers prints:

Philosopher 1 is -1, progress: 0%
Philosopher 2 is -1, progress: 0%

and so on. If in update function I would do philosophers[0].progress++, it would start to increment the progress. So I guess the problem is that if the thread in philosopher (live() function) changes something, the change does not appear in the global vector. Is there a way to change that behaviour?

Developer Guy
  • 2,318
  • 6
  • 19
  • 37
minecraftplayer1234
  • 2,127
  • 4
  • 27
  • 57

1 Answers1

0

Process of creating threads is your issue.

for (auto i = 0; i < 4; i++) {
    philosophers.push_back(philosopher(i + 1, table, table.forks[i], table.forks[i + 1]));
}

in above code you create philosopher object which is inserted into vector. In constructor of philosopher you create thread

    philosopher(int _id, dining_philosophers &table_ref, fork l, fork r)
    : id(_id), left_fork(l), right_fork(r), table(table_ref),
      t(&philosopher::live, this) {} // <<<<<---------------------

so t thread was created and it took as thread function - live method, and this method is being called on this object.

After calling

philosophers.push_back(philosopher(i + 1, table, table.forks[i], table.forks[i + 1]));

temporary object philosopher in push_back method is moved, so moved object gets new value of this pointer. While calling push_back method reallocation of memory occurs too ,so all created objectes are moved. This way of constructing objects is issue - thread function refers to object which doesn't exist, object was moved so its this pointer is invalid.

For me you should create all objects and then you can start thread.

1) remove

   t(&philosopher::live, this) {}

from constructor

2) add new method in philosopher class to start thread

void startTask () {
    t = thread(&philosopher::live,this);
}

3) create and start threads

    for (auto i = 0; i < 4; i++) {
    philosophers.push_back(
        philosopher(i + 1, table, table.forks[i], table.forks[i + 1]));
}
philosophers.push_back(
    philosopher(5, table, table.forks[4], table.forks[0]));
for (int i = 0; i < 5; ++i)
   philosophers[i].startTask();

And the last issue, your current constructor looks like

philosopher(int _id, dining_philosophers &table_ref, fork l, fork r)
    : id(_id), left_fork(l), right_fork(r), table(table_ref),

left_fork and right_fork are references, now you bind reference to temporary objects - l and r, change it to

philosopher(int _id, dining_philosophers &table_ref, fork& l, fork& r)
    : id(_id), left_fork(l), right_fork(r), table(table_ref),
rafix07
  • 20,001
  • 3
  • 20
  • 33