操作系统 | OS 经典同步问题之生产者-消费者问题,哲学家进餐问题

1. 生产者-消费者问题

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。

利用 记录型信号量 解决生产者-消费者问题

// 初始化信号量
semaphore full = 0;   // 满缓冲区数目
semaphore empty = n;  // 空缓冲区数目
semaphore mutex = 1;  // 对缓冲区操作的互斥信号量

// 生产者
producer() {
    while(1) {
        produce an item in nextp;  // nextp 为临时缓冲区
        
        P(empty);   // 申请一个空缓冲区
        P(mutex);   // 申请使用缓冲区,进入区
        put the item into buffer;   // 临界区
        V(mutex);   // 缓冲区使用完毕,释放互斥信号量
        V(full);    // 增加一个满缓冲区
    }
}

// 消费者
consumer() {
    while(1) {
        P(full);    // 申请一个满缓冲区
        P(mutex);   // 申请使用缓冲区
        取走产品;   // 临界区
        V(mutex);   // 缓冲区使用完毕,释放互斥信号量
        V(empty);   // 增加一个空缓冲区
        consume the item;  // 消费掉产品
    }
}

2. 哲学家进餐问题

有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。

约束条件
(1) 只有拿到两只筷子时,哲学家才能吃饭。
(2) 如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3) 任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。

利用 AND 信号量机制 解决哲学家进餐问题

// 五个哲学家进餐问题
semaphore chopstick[5] = {1, 1, 1, 1, 1};  // 5 根筷子信号量初值都是 1

philosopher() {
    while(1) {
        think;  // 思考
        // 想吃饭
        Swait(chopstick[i], chopstick[(i + 1) % 5]); // 要求每个哲学家先获得两个临界资源(筷子)后方能进餐
        eat;   // 吃
        Signal(chopstick[i], chopstick[(i + 1) % 5]);  // 放下筷子,释放资源
    }
}

3. 吸烟者问题

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉,但是要卷起并抽掉一支烟,需要三种材料:烟草、纸、胶水。三个抽烟者中,每一个第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者无限提供三种材料,供应者每次将两种材料放桌子上,拥有剩下材料的抽烟者卷一支烟并抽掉它,并给供应者一个信号完成了,供应者就会把另外两种材料再放桌子上,这个过程一直重复(三个抽烟者轮流抽烟)

组合一:纸 + 胶水
组合二:烟草 + 胶水
组合三:纸 + 烟草

在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 生产者消费者问题 生产者消费者问题是一种经典同步问题,其中生产者线程将数据添加到共享缓冲区中,而消费者线程从该缓冲区中获取数据。为了避免竞争条件和死锁,我们需要使用信号量来解决这个问题。 使用两个信号量,一个用于表示缓冲区是否为空,另一个用于表示缓冲区是否已满。当缓冲区已满时,生产者将等待,直到缓冲区不再满。当缓冲区为空时,消费者将等待,直到缓冲区中有更多数据。 以下是用信号量解决生产者消费者问题的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; sem_t empty, full; int in = 0, out = 0; void *producer(void *arg) { int item; while (1) { item = rand() % 100; sem_wait(&empty); buffer[in] = item; printf("Producer produced item %d at %d\n", item, in); in = (in + 1) % BUFFER_SIZE; sem_post(&full); } } void *consumer(void *arg) { int item; while (1) { sem_wait(&full); item = buffer[out]; printf("Consumer consumed item %d at %d\n", item, out); out = (out + 1) % BUFFER_SIZE; sem_post(&empty); } } int main() { sem_init(&empty, 0, BUFFER_SIZE); sem_init(&full, 0, 0); pthread_t producer_thread, consumer_thread; pthread_create(&producer_thread, NULL, producer, NULL); pthread_create(&consumer_thread, NULL, consumer, NULL); pthread_join(producer_thread, NULL); pthread_join(consumer_thread, NULL); sem_destroy(&empty); sem_destroy(&full); return 0; } ``` 2. 哲学家进餐问题 哲学家进餐问题是一个经典同步问题,其中有五个哲学家围坐在圆桌旁,每个哲学家需要交替地思考和进餐。他们之间共享五个叉子,每个哲学家需要两个叉子才能进餐。 使用信号量来解决哲学家进餐问题。每个叉子都有一个信号量,当哲学家想要使用叉子时,他必须先获取左边的叉子,然后获取右边的叉子。当哲学家用完叉子时,他将释放两个叉子的信号量。 以下是用信号量解决哲学家进餐问题的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define NUM_PHILOSOPHERS 5 sem_t forks[NUM_PHILOSOPHERS]; pthread_mutex_t mutex; void *philosopher(void *arg) { int id = *(int *)arg; int left = id; int right = (id + 1) % NUM_PHILOSOPHERS; while (1) { // think printf("Philosopher %d is thinking\n", id); sleep(rand() % 3); // pick up forks pthread_mutex_lock(&mutex); sem_wait(&forks[left]); sem_wait(&forks[right]); pthread_mutex_unlock(&mutex); printf("Philosopher %d is eating\n", id); sleep(rand() % 3); // put down forks sem_post(&forks[left]); sem_post(&forks[right]); } } int main() { int ids[NUM_PHILOSOPHERS]; pthread_t philosophers[NUM_PHILOSOPHERS]; pthread_mutex_init(&mutex, NULL); for (int i = 0; i < NUM_PHILOSOPHERS; i++) { sem_init(&forks[i], 0, 1); ids[i] = i; pthread_create(&philosophers[i], NULL, philosopher, &ids[i]); } for (int i = 0; i < NUM_PHILOSOPHERS; i++) { pthread_join(philosophers[i], NULL); sem_destroy(&forks[i]); } pthread_mutex_destroy(&mutex); return 0; } ``` 3. 读者写者问题 读者写者问题是一个经典同步问题,其中有多个读者和写者同时访问共享资源。多个读者可以同时读取共享资源,但是只有一个写者可以写入共享资源。当写者正在写入共享资源时,其他读者和写者都需要等待。 使用信号量来解决读者写者问题。使用两个信号量,一个用于表示共享资源是否正在被写入,另一个用于表示读者数量。当写者正在写入共享资源时,所有请求访问共享资源的读者和写者都需要等待。当有读者正在读取共享资源时,所有写者都需要等待。当没有写者正在写入共享资源并且没有读者正在读取共享资源时,写者可以开始写入共享资源。 以下是用信号量解决读者写者问题的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define NUM_READERS 5 #define NUM_WRITERS 2 int shared_resource = 0; sem_t resource_mutex, read_mutex; int num_readers = 0; void *reader(void *arg) { while (1) { sem_wait(&read_mutex); num_readers++; if (num_readers == 1) { sem_wait(&resource_mutex); } sem_post(&read_mutex); printf("Reader read value %d\n", shared_resource); sleep(rand() % 3); sem_wait(&read_mutex); num_readers--; if (num_readers == 0) { sem_post(&resource_mutex); } sem_post(&read_mutex); } } void *writer(void *arg) { while (1) { sem_wait(&resource_mutex); printf("Writer wrote value %d\n", ++shared_resource); sleep(rand() % 3); sem_post(&resource_mutex); } } int main() { pthread_t readers[NUM_READERS], writers[NUM_WRITERS]; sem_init(&resource_mutex, 0, 1); sem_init(&read_mutex, 0, 1); for (int i = 0; i < NUM_READERS; i++) { pthread_create(&readers[i], NULL, reader, NULL); } for (int i = 0; i < NUM_WRITERS; i++) { pthread_create(&writers[i], NULL, writer, NULL); } for (int i = 0; i < NUM_READERS; i++) { pthread_join(readers[i], NULL); } for (int i = 0; i < NUM_WRITERS; i++) { pthread_join(writers[i], NULL); } sem_destroy(&resource_mutex); sem_destroy(&read_mutex); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值