现代操作系统:进程与线程(十一)

2.5 Classical IPC Problems经典IPC问题

2.5.1 The Producer-Consumeror Bounded BufferProblem

/

2.5.2 The Dining Philosophers Problem哲学家进餐问题

这是迪杰斯特拉的一个经典问题,涉及到每个哲学家的生活:

 

Loop forever

Think

Get Hungry

Eat

 

 

吃饭这个步骤包括以下内容:

  • 5位哲学家坐在圆桌旁;
  • 每个哲学家都有一盘意大利面;
  • 每个盘子之间有一个叉子;
  • 哲学家吃饭要用两把叉子;

 

您使用什么算法来访问共享资源(叉子)?

  • 显而易见的解决方案(拿右边的;拿左边的;吃饭;放下右边的;放下左边的)=死锁。
  • 围绕所有序列化的大锁。
  • 书中有很好的代码。

 

提及“用餐哲学家”问题而不给出解决方案的目的是让人感觉一下协调问题是什么样子的。这本书也给了其他人,解决办法将在后续课程中介绍。如果你感兴趣,请看这里。

 

#include <pthread.h>

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <semaphore.h>

 

#define N 5

#define LEFT (i+N-1)%N

#define RIGHT (i+1)%N

#define THINKING 0

#define HUNGRY 1

#define EATING 2

 

sem_t semArray[N];

int status[N];

pthread_mutex_t lock;

pthread_t threadIDArray[N];

 

void test(int i)

{

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

    {

        status[i] = EATING;

        sem_post(&semArray[i]);

    }

}

 

void take_forks(int i)

{

    pthread_mutex_lock(&lock);

    status[i] = HUNGRY;

    test(i);

    pthread_mutex_unlock(&lock);

    sem_wait(&semArray[i]);

    printf("philosopher %d take No.%d and No.%d forks!\n", i, i, i + 1);

}

 

void put_forks(int i)

{

    pthread_mutex_lock(&lock);

    status[i] = THINKING;

    test(LEFT);

    test(RIGHT);

    pthread_mutex_unlock(&lock);

}

 

void* PhilosopherActionFunction(void* params)

{

    int philosopherID = *((int*)params);

    printf("philosopher %d start Thinking!\n", philosopherID);

    sleep(4);

    while (1)

    {

        printf("philosopher %d get hungry!\n", philosopherID);

        take_forks(philosopherID);

        sleep(3);

        put_forks(philosopherID);

        printf("philosopher %d put forks!\n", philosopherID);

        int thinkTime = rand() % 10;

        sleep(thinkTime);

    }

 

    pthread_exit(NULL);

}

 

int main()

{

    int i = 0;

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

    {

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

        status[i] = THINKING;

    }

 

    pthread_mutex_init(&lock, NULL);

 

    int a = 0, b = 1, c = 2, d = 3, e = 4;

    pthread_create(&threadIDArray[a], NULL, PhilosopherActionFunction, (void*)&a);

    pthread_create(&threadIDArray[b], NULL, PhilosopherActionFunction, (void*)&b);

    pthread_create(&threadIDArray[c], NULL, PhilosopherActionFunction, (void*)&c);

    pthread_create(&threadIDArray[d], NULL, PhilosopherActionFunction, (void*)&d);

    pthread_create(&threadIDArray[e], NULL, PhilosopherActionFunction, (void*)&e);

 

    pthread_join(threadIDArray[a], NULL);

    pthread_join(threadIDArray[b], NULL);

    pthread_join(threadIDArray[c], NULL);

    pthread_join(threadIDArray[d], NULL);

    pthread_join(threadIDArray[e], NULL);

 

    return 0;

}

 

 

Homework 14

In the solution to the dining philosophers problem, why is the state variable set to HUNGRY int the procedure take_forks? 在用餐哲学家问题的解决方案中,为什么状态变量在take_fork的过程中设置为HUNGRY?

        If a philosopher blocks, neighbors can later see that she is hungry by checking his state, in test, so he can be awakened when the forks are available. 如果一个哲学家被阻塞后,它的邻居可以根据这位哲学家当前处在的饥饿状态,判断其是否可以进行进餐活动,当其左右两侧的哲学家都不处于EATING状态时他就可以被唤醒成功得到叉子。

原文链接:https://blog.csdn.net/weixin_43913541/article/details/103059978

 

Consider the procedure put_forks. Suppose that the variable state[i] was set to THINKING after the two calls to test, rather then before. How would this change affect the solution? 考虑put_forks方法。假设state[i]在两次测试调用之后被设置为THINKING,而不是在之前,这种变化将如何影响解决方案?

        产生死锁!test(i)永远也无法被测试通过。The change would mean that after a philosopher stopped eating, neither of his neighbors could be chosen next. In fact, they would never be chosen. Suppose that philosopher 2 finished eating. He would run test for philosophers 1 and 3, and neither would be started, even though both were hungry and both forks were available. Similarly, if philosopher 4 finished eating, philosopher 3 would not be started. Nothing would start him.

 

2.5.3 The Readers and Writers Problem读者-写者问题

        在读者-写者问题中,我们有两类进程/线程:

  • Reader(读者)可以与其他进程/线程并发工作;
  • Writer(写者)不能与其他进程/线程并发工作;

一块区域可以同时被多个进程读,但是只能被一个进程写,即当写进程进入半临界区时其中不能有任何其他的读进程和写进程,当读进程进入半临界区时,其中可以有任意数量的读进程但是不能有写进程。在数据库中,对读进程加的锁称为S锁,写进程加的锁称为X锁。

问题是:

  1. 防止两个Writer同时进行写入;
  2. 防止Writer和Reader同时进行写入和读取;
  3. 当没有Writer写入时可允许多个Reader进行读取;
  4. 在一定程度上确保公平;

读写问题的解决方案在多处理器操作系统和数据库系统中非常有用。简单的解决方法是将所有进程视为写入器,在这种情况下问题会减少为互斥(P和V),但是简单解决方法的缺点是放弃了读取器并发性,更多信息请参见上面的网页。

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#include <semaphore.h>

#include <string.h>

 

#define M 4

#define N 2

#define LENGTH 64

 

sem_t readerSem;

sem_t writerSem;

int readingNumber = 0;

pthread_t threadList[M + N];

 

char buffer[LENGTH] = "Hello World!";

char* stringSet[] = {"Hello World", "Hello Thank you!", "Don't Hurry", "Just Do It", "Harry Potter", "I'am not sure!"};

 

void* ReaderThread(void* params)

{

    int readerID = *((int*)params);

    char threadBuffer[LENGTH];

    while (1)

    {

        sem_wait(&readerSem);

        readingNumber += 1;

        if (readingNumber == 1) sem_wait(&writerSem);

        sem_post(&readerSem);

 

        strcpy(threadBuffer, buffer);

        printf("Reader No.%d read [%s]\n", readerID, threadBuffer);

 

        sem_wait(&readerSem);

        readingNumber -= 1;

        if (readingNumber == 0) sem_post(&writerSem);

        sem_post(&readerSem);

 

        int sleepTime = rand() % 20;

        sleep(sleepTime);

    }

 

    pthread_exit(NULL);

}

 

void* WriterThread(void* params)

{

    int writerID = *((int*)params);

    while (1)

    {

        int changeToID = rand() % 6;

        sem_wait(&writerSem);

        printf("Writer No.%d start Write!\n", writerID);

        strcpy(buffer, stringSet[changeToID]);

        printf("Writer No.%d Write Success!\n", writerID);

        sem_post(&writerSem);

        sleep(changeToID);

    }

 

    pthread_exit(NULL);

}

 

int main()

{

    sem_init(&readerSem, 0, 1);

    sem_init(&writerSem, 0, 1);

 

    int* readerIDList = (int*)malloc(M * sizeof(int));

    int* writerIDList = (int*)malloc(N * sizeof(int));

    int i = 0;

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

    {

        readerIDList[i] = i + 1;

        pthread_create(&threadList[i], NULL, ReaderThread, (void*)&readerIDList[i]);

    }

 

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

    {

        writerIDList[i] = i + 1;

        pthread_create(&threadList[M + i], NULL, WriterThread, (void*)&writerIDList[i]);

    }

 

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

    {

        pthread_join(threadList[i], NULL);

    }

   

    free(readerIDList);

    free(writerIDList);

}

 

变体:

writer-priority 写者优先的读者/写者问题

reader-priority 读者优先的读者/写者问题

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#include <semaphore.h>

#include <string.h>

 

#define M 4

#define N 2

#define LENGTH 64

 

sem_t countSem;

sem_t bufferSem;

sem_t writerPrioritySem;

int readingNumber = 0;

int otherCanEnter = 1;

pthread_t threadList[M + N];

 

char buffer[LENGTH] = "Hello World!";

char* stringSet[] = {"Hello World", "Hello Thank you!", "Don't Hurry", "Just Do It", "Harry Potter", "I'am not sure!"};

 

void* ReaderThread(void* params)

{

    int readerID = *((int*)params);

    char threadBuffer[LENGTH];

    while (1)

    {

        while (otherCanEnter == 0) {};

 

        sem_wait(&countSem);

        readingNumber += 1;

        if (readingNumber == 1) sem_wait(&bufferSem);

        sem_post(&countSem);

 

        strcpy(threadBuffer, buffer);

        printf("Reader No.%d read [%s]\n", readerID, threadBuffer);

 

        sem_wait(&countSem);

        readingNumber -= 1;

        if (readingNumber == 0) sem_post(&bufferSem);

        sem_post(&countSem);

 

        int sleepTime = rand() % 2;

        sleep(sleepTime);

    }

 

    pthread_exit(NULL);

}

 

void* WriterThread(void* params)

{

    int writerID = *((int*)params);

    while (1)

    {

        int changeToID = rand() % 6;

 

        sem_wait(&writerPrioritySem);

        otherCanEnter = 0;

 

        sem_wait(&bufferSem);

        printf("Writer No.%d start Write!\n", writerID);

        strcpy(buffer, stringSet[changeToID]);

        printf("Writer No.%d Write Success!\n", writerID);

        sem_post(&bufferSem);

 

        otherCanEnter = 1;

        sem_post(&writerPrioritySem);

       

        sleep(changeToID);

    }

 

    pthread_exit(NULL);

}

 

int main()

{

    sem_init(&countSem, 0, 1);

    sem_init(&bufferSem, 0, 1);

    sem_init(&writerPrioritySem, 0, 1);

 

    int* readerIDList = (int*)malloc(M * sizeof(int));

    int* writerIDList = (int*)malloc(N * sizeof(int));

    int i = 0;

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

    {

        readerIDList[i] = i + 1;

        pthread_create(&threadList[i], NULL, ReaderThread, (void*)&readerIDList[i]);

    }

 

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

    {

        writerIDList[i] = i + 1;

        pthread_create(&threadList[M + i], NULL, WriterThread, (void*)&writerIDList[i]);

    }

 

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

    {

        pthread_join(threadList[i], NULL);

    }

   

    free(readerIDList);

    free(writerIDList);

}

 

2.5.4 Summary of 2.3 and 2.5 2.32.5的总结

我们从一个微妙的漏洞(x++和x-的错误答案)开始,并使用它来激发临界区和互斥问题,为此我们提供了名为Peterson的一个(软件)解决方案。

然后我们定义了(二元)信号量,并证明了信号量很容易解决临界区问题,而且不需要知道有多少进程在竞争临界段,我们给出了一个使用Test-and-Set实现的二元信号量。

然后我们给出了信号量的定义(它不是一个实现),并对这个定义进行了变形,以获得计数信号量(或广义信号量)的定义,对此我们给出了NO实现。我断言计数信号量可以用两个二进制信号量来实现,并给出了一个引用。

我们定义了生产者-消费者(或有界缓冲区)问题,并证明了它可以使用计数信号量(和二进制信号量,这是计数信号量的一种特殊情况)来解决。最后我们简要讨论了其他一些经典问题,但没有给出(完整的)解决方案。

posted on 2022-01-06 15:06  ThomasZhong  阅读(48)  评论(0编辑  收藏  举报

导航