哲学家就餐问题C/C++实现

2,556 阅读4分钟

Classical IPC Problems - The Dining Philosophers Problem

Dining Philosopher Problem Using Semaphores

Refer to Dining Philosopher Problem Using Semaphores.

如下图所示,哲学家需要左右两把叉子才能进食。五把叉子可以允许两位哲学家进行就餐。

Semaphore Solution to Dining Philosopher –

process P[i]
 while true do
   {  THINK;
      PICKUP(CHOPSTICK[i], CHOPSTICK[i+1 mod N]);
      EAT;
      PUTDOWN(CHOPSTICK[i], CHOPSTICK[i+1 mod N])
   }

where N is the number of Philosophers.

There are three states of philosopher : THINKING, HUNGRY and EATING. Here there are two semaphores : Mutex and a semaphore array for the philosophers. Mutex is used such that no two philosophers may access the pickup or putdown at the same time. The array is used to control the behavior of each philosopher. But, semaphores can result in deadlock due to programming errors.

/* Dining Philosopher Problem Using Semaphores */

#include <pthread.h> 
#include <semaphore.h> 
#include <stdio.h> 

#define N 5 
#define THINKING 2 
#define HUNGRY 1 
#define EATING 0 
#define LEFT (phnum + N - 1) % N 
#define RIGHT (phnum + 1) % N 

int state[N]; // 数组state跟踪每一个哲学家是在进餐、思考还是饥饿状态
int phil[N] = { 0, 1, 2, 3, 4 };

sem_t mutex; //  Mutex is used such that no two philosophers may access the pickup or putdown at the same time.
sem_t S[N]; // The array is used to control the behavior of each philosopher. But, semaphores can result in deadlock due to programming errors.

void test(int phnum)
{
    if (state[phnum] == HUNGRY
        && state[LEFT] != EATING
        && state[RIGHT] != EATING) { // 当哲学家phnum的左右两位邻居哲学家不在进餐状态,则可以获取左右两边的叉子
        // state that eating 
        state[phnum] = EATING;

        sleep(2); // 这里的sleep是为了什么?

        printf("Philosopher %d takes fork %d and %d\n",
            phnum + 1, LEFT + 1, phnum + 1); // 为什么不直接输出phnum 而要输出LEFT+1?

        printf("Philosopher %d is Eating\n", phnum + 1);

        // sem_post(&S[phnum]) has no effect 
        // during takefork 
        // used to wake up hungry philosophers 
        // during putfork ???
        sem_post(&S[phnum]);
    }
}

// take up chopsticks 
void take_fork(int phnum)
{

    sem_wait(&mutex); // 进入临界区

    // state that hungry 
    state[phnum] = HUNGRY; // 记录哲学家phnum处于饥饿状态

    printf("Philosopher %d is Hungry\n", phnum + 1);

    // eat if neighbours are not eating 
    test(phnum); // 尝试获取两把叉子

    sem_post(&mutex); // 离开临界区

    // if unable to eat wait to be signalled 
    sem_wait(&S[phnum]); // 如果得不到需要的叉子则阻塞
    // 等到了之后就sleep吗?
    // 一个哲学界对应一个thread
    // 没等到邻居put_forks的post,则阻塞在这里,不作下一轮take_forks的test?

    sleep(1);
}

// put down chopsticks 
void put_fork(int phnum)
{

    sem_wait(&mutex);

    // state that thinking 
    state[phnum] = THINKING;

    printf("Philosopher %d putting fork %d and %d down\n",
        phnum + 1, LEFT + 1, phnum + 1);
    printf("Philosopher %d is thinking\n", phnum + 1);

    test(LEFT);
    test(RIGHT);

    sem_post(&mutex);
}

void* philospher(void* num)
{

    while (1) {

        int* i = num;
        
        /*
        #include <unistd.h>

        unsigned int sleep(unsigned int seconds); // sleep - sleep for a specified number of seconds
        */

        sleep(1);

        take_fork(*i);

        sleep(0); // Sleep(0) 的意义是放弃当前线程执行的时间片,把自身放到等待队列之中。这时其它的线程就会得到时间片进行程序的程序。

        put_fork(*i);
    }
}

int main()
{

    int i;
    pthread_t thread_id[N];

    /*
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
    The pshared argument indicates whether this semaphore is to be shared
    between the threads of a process (value 0), or between processes (nonzero).

    If pshared has the value 0, then the semaphore is shared between the
    threads of a process, and should be located at some address that is
    visible to all threads (e.g., a global variable, or a variable
    allocated dynamically on the heap).
    */

    // initialize the semaphores 
    sem_init(&mutex, 0, 1);


    for (i = 0; i < N; i++)

        sem_init(&S[i], 0, 0);

    for (i = 0; i < N; i++) {
        /*
        int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
        */
        // create philosopher processes 
        pthread_create(&thread_id[i], NULL,
            philospher, &phil[i]);

        printf("Philosopher %d is thinking\n", i + 1);
    }

    for (i = 0; i < N; i++)
        /*
        int pthread_join(pthread_t thread, void **retval);
        */
        pthread_join(thread_id[i], NULL);
}

输出

Philosopher 1 is thinking
Philosopher 2 is thinking
Philosopher 3 is thinking
Philosopher 4 is thinking
Philosopher 5 is thinking
Philosopher 1 is Hungry
Philosopher 2 is Hungry
Philosopher 5 is Hungry
Philosopher 3 is Hungry
Philosopher 4 is Hungry
Philosopher 4 takes fork 3 and 4
Philosopher 4 is Eating
Philosopher 4 putting fork 3 and 4 down
Philosopher 4 is thinking
Philosopher 3 takes fork 2 and 3
Philosopher 3 is Eating
Philosopher 5 takes fork 4 and 5
Philosopher 5 is Eating
Philosopher 3 putting fork 2 and 3 down
Philosopher 3 is thinking
Philosopher 2 takes fork 1 and 2
Philosopher 2 is Eating
Philosopher 4 is Hungry
Philosopher 5 putting fork 4 and 5 down
Philosopher 5 is thinking
Philosopher 4 takes fork 3 and 4
Philosopher 4 is Eating
Philosopher 3 is Hungry
Philosopher 2 putting fork 1 and 2 down
Philosopher 2 is thinking
Philosopher 1 takes fork 5 and 1
Philosopher 1 is Eating
Philosopher 5 is Hungry
Philosopher 4 putting fork 3 and 4 down
Philosopher 4 is thinking
......

Dining-Philosophers Solution Using Monitors

Refer to Dining-Philosophers Solution Using Monitors

Monitors in Process Synchronization

  • It is the collection of condition variables and procedures combined together in a special kind of module or a package.
  • The processes running outside the monitor can’t access the internal variable of the monitor but can call procedures of the monitor.
  • Only one process at a time can execute code inside monitors.
/* Dining-Philosophers Solution Using Monitors  */

#define N 5 
#define THINKING 2 
#define HUNGRY 1 
#define EATING 0 
#define LEFT (i + N - 1) % N 
#define RIGHT (i + 1) % N 

// Dining-Philosophers Solution Using Monitors 
monitor DP
{
    status state[N]; // 数组state跟踪每一个哲学家是在进餐、思考还是饥饿状态
    condition self[N];

    // Pickup chopsticks 
    Pickup(int i)
    {
        // indicate that I’m HUNGRY 
        state[i] = HUNGRY;

        // set state to EATING in test() 
        // only if my left and right neighbors  
        // are not EATING 
        test(i);

        // if unable to eat, wait to be signaled 
        if (state[i] != EATING)
            self[i].wait;
    }

    // Put down chopsticks 
    Putdown(int i)
    {

        // indicate that I’m THINKING 
        state[i] = THINKING;

        // if right neighbor R=(i + 1) % N is HUNGRY and 
        // both of R’s neighbors are not EATING, 
        // set R’s state to EATING and wake it up by  
        // signaling R’s CV 
        test(LEFT);
        test(RIGHT);
    }

    test(int i)
    {

        if (state[i] == HUNGRY
            && state[LEFT] != EATING
            && state[RIGHT] != EATING) {

            // indicate that I’m EATING 
            state[i] = EATING;

            // signal() has no effect during Pickup(), 
            // but is important to wake up waiting 
            // HUNGRY philosophers during Putdown() 
            self[i].signal();
        }
    }

    init()
    {

        // Execution of Pickup(), Putdown() and test() 
        // are all mutually exclusive, 
        // i.e. only one at a time can be executing 
        // 因为写在monitor里面,每次确保只有一个线程可以使用monitor,从而不需要mutex?
for
    i = 0 to N

    // Verify that this monitor-based solution is 
    // deadlock free and mutually exclusive in that 
    // no 2 neighbors can eat simultaneously 
    state[i] = THINKING;
}
} // end of monitor