操作系统之生产者与消费者实验

操作系统实验二

一、 实验内容

问题描述:

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。即有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池:生产者进程从文件中读取一个数据,并将它存放到一个缓冲区中; 消费者进程从一个缓冲区中取走数据,并输出此数据。生产者和消费者之间必须保持同步原则:不允许消费者进程到一个空缓冲区去取产品;也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。

实验要求:

1.生产者进程(或者线程模拟进程)3个以上;
2.公共缓冲池大小5个以上;
3.消费者进程3个以上;
4.任意顺序启动生产者和消费者,验证里面存在的互斥与同步。
(1)先启动全部生产者,后启动全部消费者。
(2)先启动全部消费者,后启动全部生产者。
(3)先启动部分生产者,后启动部分消费者。
(4)先启动部分消费者,后启动部分生产者。

二、 实验设计思想

问题分析:

1.生产者-消费者之间的同步关系表现为:一旦旦缓冲池中所有缓冲区均装。满产品,生产者必须等待消费者提供空缓冲区;一旦缓冲池中所有缓冲区全为空,消费者必须等待生产者提供满缓冲区。这是两个“等信号”的操作,可以用两个P操作来实现,由于所等的两个信号也不一样,所以这两个P操作是对两个不同的信号量进行的。对应的“发信号”的操作,即两个v操作,就应该出现在彼此的合作伙伴进程中,亦即,每当消费者从装有产品的满缓冲区中取走产品时,应该给生产者发一个信号,告诉它已有一个空缓冲区;每当生产者把产品放入空缓冲区时,应该给消费者发一个信号,告诉它已有一个满缓冲区。
2.生产者-消费者之间还有互斥关系。由于缓冲池是临界资源,所以任何进程在对缓冲区进行存取操作时都必须和其他进程互斥进行。这可以设一个公用的初值为1的互斥信号量,以保证同时只有一个进程进入临界区。并且,互斥关系主要是对同一类进程而言的,该问题中如果只有一对生产者-消费者,且缓冲池简化为只有一个单元的缓冲区时,则不必考虑其互斥问题。因为一对生产者-消费者无论是同步地访问所共享的缓冲池,还是互斥地访问,都不会引起对缓冲池的同时访问。但如果有多个生产者(或消费者),则他们有可能同时对缓冲池进行存(或取)操作,因此必须考虑互斥问题。

设计思路:

1.主函数分别调用test1()、test2()、test3()、test4()四个函数,实现4种任意顺序启动生产者和消费者,验证里面存在的互斥与同步。
2.在被调函数中:通过定义tread类数组变量,分别创建5个生产者和消费者,并调用join()函数的作用让主线程的等待该⼦线程完成。
3.定义信号量Semaphore类:包含成员变量:互斥锁mut、条件变量condition、信号量count。成员方法:构造函数、P操作、V操作。
(1)P方法:信号量减1,若count≥0,继续执行;若count<0,调用者进程调用wait()方法进入到一个和该对象相关的等待池中。
(2)V方法:信号量加1,若count0,继续执行;若count≤0,调用者进程调用notify_one() 方法唤醒等待队列中的第一个线程表示释放该资源,即使用完资源后归还资源。
4.所用信号量设置如下:
(1)同步信号量_empty,初值为N=10,表示消费者已把缓冲池中全部产品取走,有10个空缓冲区可用。
(2)同步信号量_full,初值为0,表示生产者尚未把产品放入缓冲池,有0个
满缓冲区可用。
(3)互斥信号量_mutex,初值为1,以保证同时只有一个进程能够进入临界区,访问缓冲池。
5.分别创建生产者producter()函数和消费者consumer()函数。
(1)如果一开始生产者进程抢先执行,则因为缓冲池全空,它生产出产品并执行_empty.P()后能继续向下执行;直至缓冲池全满时,它将阻塞在_empty信号量上,等待消费者进程执行_empty.V()来唤醒自己。
(2)如果一开始消费者进程抢先执行,则因为缓冲池全空,它首先因因执行_full.P()而阻塞,直至缓冲池中有产品时。就这样,生产者和消费者进程在并发执行过程中,时而你等我,时而我等你,同步地向前推进。而它们要进缓冲区都必须先执行_mutex.P(),出缓冲区后都执行 _mutex.V(),保证了对缓冲区的互斥使用。
6.运行程序查看结果正确性,会看到这两个线程产者和消费者在轮流抢占CPU的执行权来输出他们各自的内容。

三、 实验结果及分析

在这里插入图片描述

结果分析:

在生产者消费者问题实验中,通过模拟生产者和消费者进程的交互,以验证并发进程中的同步和互斥等操作。以下是对实验结果的分析:
(1)同步信号量作用:实验结果表明,使用同步信号量(_full和_empty)能够有效控制生产者和消费者进程的执行顺序。当缓冲区满时,生产者被阻塞;当缓冲区空时,消费者被阻塞。这避免了生产者持续往缓冲区放数据,以及消费者持续从缓冲区取数据的情况,保证了生产者和消费者进程的协调运行。
(2)互斥信号量作用:通过互斥信号量(_mutex)的作用,实验中保证了各进程能够互斥地使用环形缓冲区。这意味着在任何时刻,只有一个进程能够访问环形缓冲区,从而避免了多个进程同时读写导致的数据混乱问题。
(3) 死锁问题:实验中特别关注了死锁问题。如果_full和_mutex、_empty的pv操作的顺序不当,都可能引发死锁。例如,当缓冲区满时,生产者进程先执行_mutex.p()操作并成功,再执行_empty.p()操作时失败并进入阻塞状态,期待消费者执行_empty.v()唤醒。在此之前,其他生产者和消费者进程也会因无法获取_mutex.p()而进入阻塞状态,从而引发系统死锁。因此,正确的操作顺序至关重要。
(4)代码实现与运行结果:实验中通过编写并编译C++代码实现了上述逻辑。在运行过程中,完成了生产者-消费者多线程模拟过程,这表明生产者和消费者问题得到了有效解决。

四、 其他

问题解决:

1.在生产者-消费者问题中,如果缺少了同步信号量_full或同步信号量_empty,对执行结果会有何影响?
答:(1)假设如果没有同步信号量_full满缓存区数量+1的话,在生产者执行完毕后,_full依然为0,执行消费者的时候,因为_full为0,判断为现在缓冲池没有满缓存区,而进入等待状态。
(2)如果缺少同步信号量_empty,空缓冲区+1的话,因为empty初始值为n,执行n次生产者之后empty为0,之后会让生产者一直处于等待状态。
2.在生产者-消费者问题中,如果将两个v操作对调,是否会导致问题变得不可解决?
答:如果将两个V操作对调,即先执行V操作再执行P操作,可能会导致生产者或消费者在尝试访问缓冲区时发生冲突。例如,如果缓冲区已满,生产者可能会先执行V操作,然后尝试向缓冲区添加数据,而此时缓冲区仍然为满状态,导致生产者无法继续执行。同样,如果缓冲区为空,消费者可能会先执行V操作,然后尝试从缓冲区获取数据,而此时缓冲区仍然为空状态,导致消费者无法继续执行。

五、实验代码

#include<iostream>
#include<queue>
#include<vector>
#include<thread>
#include<mutex>
#include<condition_variable>
using namespace std;

const int N = 10;   //缓冲池大小为10
const int SIZE_P = 5;   //生产者数量
const int SIZE_C = 5; //消费者数量
queue<int>q; // 缓冲区队列

// 信号量定义
class Semaphore
{
private:
    mutex mut;
    condition_variable condition;
    int count;
public:
    Semaphore(int count = 0)  //构造函数初始化
    {
        this->count = count ;
    };
    //成员方法P操作
    void P()
    {
        unique_lock<mutex> unique(mut); //unique_lock自动上锁解锁
        count-- ;
        if (count < 0) condition.wait(unique);
        /*
        调用wait()方法进入到一个和该对象相关的等待池中,
        进入等待队列,也就是阻塞的一种,叫等待阻塞,
        同时释放对象锁,并让出CPU资源。
        */
    }

    // V操作
    void V()
    {
        unique_lock<mutex> unique(mut);
        count++ ;
        if (count <= 0) condition.notify_one();
        //notify_one() 唤醒等待队列中的第一个线程。
    }
};

Semaphore  _mutex(1);  //互斥信号量初值为1,取值范围为(-1,0,1)
Semaphore _empty(N); //同步信号量,初值为10,表示消费者已把缓冲池中全部产品取走。
Semaphore _full(0);   //同步信号量,表示生产者尚未把产品放入缓冲池。

// 生产者
void producter(){
    for(int i =0 ; i<50; i++){
        _empty.P();//线程同步
        _mutex.P();//互斥锁
        q.push(1);
        cout << "生产者"<<this_thread::get_id() << " 生产了1件产品, ";
        cout << N - q.size()  << "个空缓冲区可用" << endl;
        _mutex.V();//互斥锁
        _full.V();//线程同步
        this_thread::sleep_for(chrono::milliseconds(1));
        //线程阻塞,当前的线程休眠1ms其他线程不休息。
    }
}

// 消费者
void consumer(){
    for(int i =0 ; i<50; i++){
        _full.P();//线程同步
        _mutex.P();//互斥锁
        int data = q.front();
        q.pop();
        cout << "消费者"<<this_thread::get_id() << " 消费了1件产品, ";
        cout << N - q.size()  << "个空缓冲区可用" << endl;
        _mutex.V();//互斥锁
        _empty.V();//线程同步
        this_thread::sleep_for(chrono::milliseconds(1));
        //线程阻塞,当前的线程休眠1ms其他线程不休息。
    }
}
void test1();
void test2();
void test3();
void test4();
int main()
{
    test1();  //先启动全部生产者,后启动全部消费者
    test2();  //先启动全部消费者,后启动全部生产者
    test3();  //先启动部分生产者,后启动部分消费者
    test4();  //先启动部分消费者,后启动部分生产者
    return 0;
}
void test1(){
    cout << "----------先启动全部生产者,后启动全部消费者----------" <<endl;
    thread a[SIZE_P],b[SIZE_C];
    for (int i = 0; i < SIZE_P ; ++i) a[i] = thread(producter);
    for (int i = 0; i < SIZE_C ; ++i) b[i] = thread(consumer);
    for (int i = 0; i < 5 ; ++i) a[i].join(),b[i].join();
}
void test2(){
    cout << "----------先启动全部消费者,后启动全部生产者----------" <<endl;
    thread a[SIZE_P],b[SIZE_C];
    for (int i = 0; i < SIZE_C ; ++i) b[i] = thread(consumer);
    for (int i = 0; i < SIZE_P ; ++i) a[i] = thread(producter);
    for (int i = 0; i < 5 ; ++i) a[i].join(),b[i].join();
}
void test3(){
    cout << "----------先启动部分生产者,后启动部分消费者----------" <<endl;
    thread a[SIZE_P],b[SIZE_C];
    for (int i = 0; i < SIZE_P-2 ; ++i) a[i] = thread(producter);
    for (int i = 0; i < SIZE_C-2 ; ++i) b[i] = thread(consumer);
    for (int i = SIZE_P-2  ; i < SIZE_P ; ++i) a[i] = thread(producter);
    for (int i = SIZE_C-2 ; i < SIZE_C; ++i) b[i] = thread(consumer);
    for (int i = 0; i < 5 ; ++i) a[i].join(),b[i].join();
}
void test4(){
    cout << "----------先启动部分消费者,后启动部分生产者----------" <<endl;
    thread a[SIZE_P],b[SIZE_C];
    for (int i = 0; i < SIZE_C-2 ; ++i) b[i] = thread(consumer);
    for (int i = 0; i < SIZE_P-2 ; ++i) a[i] = thread(producter);
    for (int i = SIZE_C-2 ; i < SIZE_C; ++i) b[i] = thread(consumer);
    for (int i = SIZE_P-2  ; i < SIZE_P ; ++i) a[i] = thread(producter);
    for (int i = 0; i < 5 ; ++i) a[i].join(),b[i].join();
}

  • 22
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值