• 操作系统-进程与线程(同步互斥典型模型-生产者,消费者模型、吸烟者问题)



    王道计算机操作系统学习笔记心得

    1.生产者,消费者模型

    生产者消费者模型概述:

    有一个缓冲区,生产者线程向缓冲区中填入数据,消费者线程从缓冲区中读取出一个数据。

    需要注意

    1. 只有当缓冲区有数据时,消费者线程才可以取出缓冲区中的数据。否则阻塞等待
    2. 只有缓冲区有空间时,生产者线程才可以向缓冲区生产数据。否则阻塞等待
    3. 缓冲区属于临界资源,线程访问时需要互斥访问。

    根据上面三种注意事项和信号量的PV操作可以分析出:

    • 缓冲区还有数据V======>P消费者线程消费

    • 生产者线程(进程)生产P<========V缓冲区还有控件

    • 临界资源互斥访问,可以使用互斥信号量来实现。或者加锁实现。

    伪代码如下:

    semaphore mutex = 1;
    //互斥信号量,实现对缓冲区的互斥访问
    semaphore empty = n;
    //同步信号量,表示空闲缓冲区的数量,n代表缓冲区的大小
    semaphore full = 0;
    //同步信号量,表示产品的数量,也即非空缓冲区的数量
    
    //生产者
    producer () {
    	while(true){
    		生产一个缓冲区数据;
    		P(empyt)//空闲信号量-1
    		
    		//互斥访问临界区
    		P(mutex)
    		把产品放入缓冲区;
    		V(mutex)
    		
    		V(full)//代表数据的信号量+1
    	}
    }
    
    //消费者
    consumer () {
    	while(true){
    		P(full);//数据信号量因为取出了要-1
    		
    		//互斥访问临界区
    		P(mutex)
    		从缓冲区取出一个数据;
    		V(mutex)
    		
    		V(empty)//缓冲区空闲增加,empyt信号量+1
    		使用缓冲区数据;
    	}
    }
    

    实例:Linux环境下C++实现环形生产者消费者模型,这里互斥操作使用互斥锁实现

    #pragma once 
    
    #include
    #include
    #include
    #include
    
    #define SIZE 32
    
    template<class T>
    class RingQueue
    {
      private:
        std::vector<T>q;
        int Cap;
    
        int b_pos;//只有生产者线程会访问,单生产者不需要保护这个变量,下面也是如此
        int d_pos;
    
        sem_t block_Num;
        sem_t data_Num;
      private:
        void P(sem_t&s)
        {
          sem_wait(&s);
        }
        void V(sem_t&s)
        {
          sem_post(&s);
        }
      public:
        RingQueue(int cap=SIZE):Cap(cap),b_pos(0),d_pos(0)
        {
          q.resize(Cap);
          sem_init(&block_Num,0,Cap);//开始空间信号量为Cap
          sem_init(&data_Num,0,0);//开始数据信号量为0
        }
        
        void Push(const T&in)//生产数据
        {
          P(block_Num);//关心空间信号量,申请空间信号量,P操作使空间信号量-1
          q[b_pos]=in;
          V(data_Num);//生产者生产了一个数据,数据信号量,V操作使数据信号量+1
          b_pos++;
          b_pos%=Cap;
        }
    
        void Pop(T&out)
        {
          P(data_Num);//当数据信号量为0时,消费者线程挂起等待,所以一定是生产者线程先运行,消费者线程后运行
          out=q[d_pos];
          V(block_Num);//消费了数据,所以空间信号量V操作使空间信号量+1
          d_pos++;
          d_pos%=Cap;
        }
    
        ~RingQueue(){
          sem_destroy(&block_Num);
          sem_destroy(&data_Num);
        }
    };
    

    多生产者,多消费者模型

    问题描述:
    A生产者向缓冲区生产A数据,A数据只能被A消费者消费。
    B生产者向缓冲区生产B数据,B数据只能被B消费者消费。

    这四个线程(进程)公用一个缓冲区,通过信号量描述上述关系。

    首先:分析同步互斥关系

    1. 缓冲区是临界资源,需要互斥访问。A,B生产者是互斥关系;A,B消费者是互斥关系。
    2. A生产者生产后,A消费者下可以消费,是同步关系
    3. 同理B生产者和B消费者也是同步关系

    伪代码:

    semaphore mutex = 1;
    //实现互斥访问缓冲区
    semaphore a = 0;
    //缓冲区中有几个A资源
    semaphore b = 0;
    //缓冲区中有几个B资源
    semaphore buff = 1;
    //缓冲区大小
    
    A(){
    	while(true){
    		生产A
    		//申请缓冲区资源
    		P(buff)
    		
    		//互斥访问临界资源
    		P(mutex)
    		将A放入缓冲区中
    		V(mutex)
    		
    		V(a)//A资源多了一个,信号量a++
    	}
    }
    
    B(){
    	while(true){
    		生者B
    		P(buff)
    		P(mutex)
    		将B放入缓冲区中
    		V(mutex)
    		V(b)
    	}
    }
    
    delA(){
    	while(true){
    		//看缓冲区中是否有自己需要的数据
    		P(a)
    		P(mutex)
    		从缓冲区取出A
    		V(mutex)
    		//缓冲区多空出一个资源
    		V(buff)
    		处理A任务
    	}
    }
    
    delB(){
    	while(true){
    		P(b)
    		P(mutex)
    		从缓冲区中取出B
    		V(mutex)
    		V(buff)
    		处理B任务
    	}
    }
    

    需要注意:
    因为此题的缓冲区大小为1,所以其实可以不设置互斥信号量mutex也可以实现所有线程互斥访问临界资源。

    如果缓冲区大小大于1,则就需要设置互斥信号量mutex实现互斥操作。

    其次:在分析同步类问题时,需要抽象成事件的发生会导致那些事件发生来分析,不要从进程(线程)的具体操作会导致什么结果分析。

    2. 吸烟者问题

    问题描述:

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

    首先分析同步互斥关系:

    1. 桌子可以看作缓冲区,进程(线程)访问需要互斥访问
    2. 供应者可以看作生产者线程,生产三种组合的数据。
      组合一:纸+胶水 组合二:烟草+胶水 组合三:烟草+纸
    3. 缓冲区是组合一,一号吸烟者运行,同步关系
    4. 同理三种组合就是三组同步关系,每一组同步关系都需要设置一个信号量
    5. 吸烟者完成后,发送消息给供应者。是同步关系

    因为,缓冲区大小为1,所以不需要这是互斥变量来保证临界资源安全。

    伪代码:

    semaphore offer1 = 0;
    //桌上组合一的数量
    semaphore offer2 = 0;
    //桌上组合二的数量
    semaphore offer3 = 0;
    //桌上组合三的数量
    semaphore finish = 0;
    //抽烟是否完成
    int i=0;
    //用来实现三个吸烟者轮流吸烟(i=(i+1)%3来实现轮流吸烟)
    
    provider (){
    	while (1){
    		if(i==0){
    			将组合一放桌上;
    			V(offer1);//通知吸烟者,资源就绪
    		}
    		else if(i==1){
    			将组合二放桌上;
    			V(offer2);
    		}
    		else{
    			将组合三放桌上;
    			V(offer3);
    		}
    		i=(i+1%3;
    		//等待吸烟者吸烟完毕后继续循环
    		P(finish);
    	}
    }
    
    smoker1 ({
    	while(1{
    		P(offer1)
    		从桌上拿走组合一;
    		卷烟;抽掉;
    		V(finish)
    	}
    }
    
    smoker2 ({
    	while(1{
    		P(offer2)
    		从桌上拿走组合二;
    		卷烟;抽掉;
    		V(finish)
    	}
    }
    
    smoker3 ({
    	while(1{
    		P(offer3)
    		从桌上拿走组合三;
    		卷烟;抽掉;
    		V(finish)
    	}
    }
    
  • 相关阅读:
    链表试题(Python实现)
    【JavaScript流程控制-分支】
    SpringBoot Application事件监听的实现方案(动态写入yml)
    Day13--搜索建议-自动获取焦点与防抖处理
    B_QuRT_User_Guide(33)
    Docker-(基础服务)-数据库安装-简单版: Redis数据库,Mysql数据库
    04.7. 前向传播、反向传播和计算图
    把字符串转换成整数[考虑溢出]
    springcloudalibaba架构(28):分布式事务解决方案
    JavaScript循环语句(for、while)
  • 原文地址:https://blog.csdn.net/dodamce/article/details/127043939