2.17、多生产者-多消费者进程

news2024/12/28 19:41:36

桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。

image-20230202201249516

生产者生产不同的产品,消费者消费相应的产品


1、问题分析

① 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。

互斥关系:

  • 对缓冲区(盘子)的访问要互斥地进行同步关系(一前一后):

同步关系(一前一后)

  1. 父亲将苹果放入盘子后,女儿才能取苹果

  2. 母亲将橘子放入盘子后,儿子才能取橘子

  3. 只有盘子为空时,父亲或母亲才能放入水果

    • “盘子为空”这个事件可以由儿子或女儿触发,事件发生后才允许父亲或母亲放水果

② 整理思路。根据各进程的操作流程确定 PV 操作的大致顺序。

互斥:在临界资源前后分别 PV

同步:前 VP

  • 在前操作后面进行 V 操作
  • 在后操作前面进行 P 操作

③ 设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。

  • (互斥信号量初值一般为 1,同步信号量的初始值要看对应资源的初始值是多少)

image-20230202202128592

2、具体实现

image-20230202202128592

image-20230202202510021

image-20230202203401161


问题:可不可以不用互斥信号量?

image-20230202203942261

分析:刚开始,儿子、女儿进程即使上处理机运行也会被阻塞。如果刚开始是父亲进程先上处理机运行,则:
父亲 P(plate),可以访问盘子 → 母亲 P(plate),阻塞等待盘子 → 父亲放入苹果 V(apple),女儿进程被唤醒,其他进程即使运行也都会阻塞,暂时不可能访问临界资源(盘子) → 女儿 P(apple) ,访问盘子,V(plate) ,等待盘子的母亲进程被唤醒→母亲进程访问盘子(其他进程暂时都无法进入临界区)>…

结论:即使不设置专门的互斥变量 mutex,也不会出现多个进程同时访问盘子的现象

原因在于

  • 本题中的缓冲区大小为 1,在任何时刻,appleorangeplate 三个同步信号量中最多只有一个是1。

  • 因此在任何时刻,最多只有一个进程的P操作不会被阻塞,并顺利地进入临界区…


若盘子容量设置为 2

父亲 P(plate),可以访问盘子 → 母亲 P(plate),可以访问盘子 → 父亲在往盘子里放苹果,同时母亲也可以往盘子里放橘子。

  • 于是就出现了两个进程同时访问缓冲区的情况,有可能导致两个进程写入缓冲区的数据相互覆盖的情况。

因此,如果缓冲区大小大于 1 1 1,就必须专门设置一个互斥信号量 mutex 来保证互斥访问缓冲区。

3、Java 案例

import java.util.Deque;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ProducerConsumerByLock {
    //互斥资源
    private static Deque<String> queueApple = new LinkedList<>();
    private static Deque<String> queueOrange = new LinkedList<>();
    private static int maxSize = 1;
    private static Lock lock = new ReentrantLock(false);
    private static Condition apple = lock.newCondition();
    private static Condition orange = lock.newCondition();
    private static Condition plate = lock.newCondition();

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture.runAsync(new Dad());
        CompletableFuture.runAsync(new Mom());
        CompletableFuture.runAsync(new Daughter());
        CompletableFuture.runAsync(new Son());
        //CompletableFuture.runAsync(new Consumer()); 产生的进程默认是守护进程
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static class Dad implements Runnable {

        //放入苹果
        public void put() {
            while (true) {
                lock.lock();
                try {
                    while (queueApple.size() == maxSize || queueOrange.size() == maxSize) {
                        System.out.println("盘子不为空, 父亲阻塞");
                        try {
                            plate.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //放入苹果
                    queueApple.offerLast("apple");
                    System.out.println("父亲放入了 1 个苹果");
                    //通知苹果的所有消费者
                    apple.signalAll();
                } finally {
                    lock.unlock();
                }
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void run() {
            put();
        }
    }

    public static class Mom implements Runnable {

        //放入橘子
        public void put() {
            while (true) {
                lock.lock();
                try {
                    while (queueOrange.size() == maxSize || queueApple.size() == maxSize) {
                        System.out.println("盘子不为空, 母亲阻塞");
                        try {
                            plate.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //放入橘子
                    queueOrange.offerLast("orange");
                    System.out.println("母亲放入了 1 个橘子");
                    //通知橘子的所有消费者
                    orange.signalAll();
                } finally {
                    lock.unlock();
                }
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void run() {
            put();
        }
    }


    public static class Son implements Runnable {

        //获取橘子
        public void get() {
            while (true) {
                String v = "";
                lock.lock();
                try {
                    while (queueOrange.isEmpty()) {
                        System.out.println("盘子为空或者没有橘子,儿子阻塞");
                        try {
                            orange.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //获取橘子
                    v = queueOrange.poll();
                    System.out.println("儿子吃了一个橘子: " + v);
                    //通知所有生产者, 父亲和母亲
                    plate.signalAll();
                } finally {
                    lock.unlock();
                }
                //防止一瞬间消费完成
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void run() {
            get();
        }
    }

    public static class Daughter implements Runnable {

        //获取苹果
        public void get() {
            String v = "";
            while (true) {
                lock.lock();
                try {
                    while (queueApple.isEmpty()) {
                        System.out.println("盘子为空或者没有苹果,女儿阻塞");
                        try {
                            apple.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //获取橘子
                    v = queueApple.poll();
                    System.out.println("女儿吃了一个苹果: " + v);
                    //通知所有生产者, 父亲和母亲
                    plate.signalAll();
                } finally {
                    lock.unlock();
                }
                //防止一瞬间消费完成
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void run() {
            get();
        }
    }
}


stdout:

父亲放入了 1 个苹果
盘子不为空, 母亲阻塞
女儿吃了一个苹果: apple
母亲放入了 1 个橘子
儿子吃了一个橘子: orange
父亲放入了 1 个苹果
盘子为空或者没有橘子,儿子阻塞
女儿吃了一个苹果: apple
母亲放入了 1 个橘子
儿子吃了一个橘子: orange
盘子为空或者没有橘子,儿子阻塞
盘子为空或者没有苹果,女儿阻塞
父亲放入了 1 个苹果
女儿吃了一个苹果: apple
母亲放入了 1 个橘子
儿子吃了一个橘子: orange
母亲放入了 1 个橘子
盘子不为空, 父亲阻塞
儿子吃了一个橘子: orange
父亲放入了 1 个苹果
女儿吃了一个苹果: apple
盘子为空或者没有橘子,儿子阻塞
母亲放入了 1 个橘子
儿子吃了一个橘子: orange
盘子为空或者没有苹果,女儿阻塞

4、总结

总结:在生产者-消费者问题中,如果缓冲区大小为 1 ,那么有可能不需要设置互斥信号量就可以实现互斥访问缓冲区的功能。

  • 当然,这不是绝对的,要具体问题具体分析。

建议:在考试中如果来不及仔细分析,可以加上互斥信号量,保证各进程一定会互斥地访问缓冲区。

  • 但需要注意的是,实现互斥的P操作一定要在实现同步的 P 操作之后,否则可能引起 “死锁” 。

解决 “多生产者-多消费者问题” 的关键在于理清复杂的同步关系。

在分析同步问题(一前一后问题)的时候不能从单个进程行为的角度来分析,要把“一前一后”发生的事看做是两种“事件”的前后关系。

比如,如果从单个进程行为的角度来考虑的话,我们会有以下结论:

  • 如果盘子里装有苹果,那么一定要女儿取走苹果后父亲或母亲才能再放入水果 \color{red}如果盘子里装有苹果,那么一定要女儿取走苹果后父亲或母亲才能再放入水果 如果盘子里装有苹果,那么一定要女儿取走苹果后父亲或母亲才能再放入水果
  • 如果盘子里装有橘子,那么一定要儿子取走橘子后父亲或母亲才能再放入水果 \color{red}如果盘子里装有橘子,那么一定要儿子取走橘子后父亲或母亲才能再放入水果 如果盘子里装有橘子,那么一定要儿子取走橘子后父亲或母亲才能再放入水果
  • 这么看是否就意味着要设置四个同步信号量分别实现这四个“一前一后”的关系了?

image-20230202205304014

正确的分析方法应该从“ 事件 \textcolor{red}{事件} 事件”的角度来考虑

  • 我们可以把上述四对 “进程行为的前后关系” 抽象为一对 “事件的前后关系
  • 盘子变空事件 → 放入水果事件。“盘子变空事件” 既可由儿子引发,也可由女儿引发;“放水果事件”"既可能是父亲执行,也可能是母亲执行。这样的话,就可以用一个同步信号量解决问题了

image-20230202205319716

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统应用编程(四)Linux多线程

本篇文章主要内容&#xff1a;Linux系统应用编程&#xff08;四&#xff09;Linux多线程一、线程和进程的区别二、Linux多线程1.线程的使用 - 创建、退出、等待2.线程的同步 - 互斥量&#xff08;1&#xff09;互斥量的理解&#xff08;略&#xff09;&#xff08;2&#xff09…

你真的会自动化测试?自动化测试技术选型抉择

自动化测试框架 在学习自动化测试或者实践自动化测试时&#xff0c;我们一定会对一个名词不陌生&#xff0c;那就是“自动化测试框架”&#xff0c;而有些人也将Selenium、Appium这样的工具也称之为“自动化测试框架”&#xff0c;那么到底自动化测试框架如何理解呢&#xff1…

多种文字翻译软件-翻译常用软件

整篇文档翻译软件 整篇文档翻译软件是一种实现全文翻译的自动翻译工具&#xff0c;它能够快速、准确地将整篇文档的内容翻译成目标语言。与单词、句子翻译不同&#xff0c;整篇文档翻译软件不仅需要具备准确的语言识别和翻译技术&#xff0c;还需要考虑上下文语境和文档格式等多…

【Linux】一文带你探究网络世界的基石

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;计算机网络…

JVM专题

JVM类加载 Java里有如下几种类加载器&#xff1a; 引导类加载器&#xff1a;负责加载支撑JVM运行的位于JRE的lib目录下的核心类库&#xff0c;比如 rt.jar、charsets.jar等 扩展类加载器&#xff1a;负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包应用程序…

一篇文章让你搞懂TypeScript中的??和?:和?.和!.是什么意思

TypeScript中的??和?:和?.和!.是什么意思&#xff1f;知识回调&#xff08;不懂就看这儿&#xff01;&#xff09;场景复现核心干货???:?.!.知识回调&#xff08;不懂就看这儿&#xff01;&#xff09; 知识专栏专栏链接TypeScript知识专栏https://blog.csdn.net/xsl_…

私有化部署GPT,告别网络困扰

最近的GPT是热火朝天&#xff0c;基本人手一个。工具用的好&#xff0c;工作5分钟&#xff0c;划水一整天。 不过最近Chat的访问越来越限制了&#xff0c;访问官网都有网络的问题&#xff0c;今天给大家介绍一个方案&#xff0c;私人独享属于自己的chat&#xff0c;不再担心想…

sdx12使能bluetooth

最后的效果&#xff1a; 1.驱动使能 apps_proc/kernel/msm-5.4/arch/arm/configs/vendor/sdxnightjar.config #add bt driver CONFIG_BTy CONFIG_MSM_BT_POWERy使用的芯片是sdx12 QCA6174A-1 管脚配置如下&#xff08;如果管脚不同&#xff0c;需要修改对应的dts&#xff09…

(十)排序算法-冒泡排序

1 排序算法 1.1 介绍 排序也称为排序算法&#xff08;Sort Algorithm&#xff09;&#xff0c;排序是将一组数据&#xff0c;依指定的顺序进行排列的过程。 1.2 排序的分类 &#xff08;1&#xff09;内部排序 指将需要处理的所有数据都加载到内部存储器中进行排序。 &…

C/C++程序设计——const关键字

1.修饰变量 1.1 作用 功能&#xff1a;不能直接被修改 const修饰变量&#xff0c;就相当于是定义了一个常量。该变量不能直接被修改&#xff0c;但是可以通过指针修改。 作用&#xff1a;便于维护、提前发现可能错误的修改 比如程序中大量使用了一个数字10&#xff0c;且不会…

GaussDB工作级开发者认证—第一章GaussDB数据库介绍

一. GaussDB概述 GaussDB是华为基于openGauss自研生态推出的企业级分布式关系型数据库。具备企业级复杂事物混合负载能力&#xff0c;同时支持分布式事务强一致性&#xff0c;同城跨AZ部署&#xff0c;数据0丢失&#xff0c;支持1000的计算节点扩展能力&#xff0c;4PB海量存储…

springcloud2.1.0整合seata1.5.2+nacos2.10(附源码)

springcloud2.1.0整合seata1.5.2nacos2.10&#xff08;附源码&#xff09; 1.创建springboot2.2.2springcloud2.1.0的maven父子工程如下&#xff0c;不过多描述&#xff1a; 搭建过程中也出现很多问题&#xff0c;主要包括&#xff1a; 1.seataServer.properties配置文件的组…

安全配置管理 (SCM):建立安全的基础

通过确保在端点中建立和维护理想的安全配置&#xff0c;让自己在安全的基础上做好准备&#xff0c;这样公司就不会因单个漏洞而分崩离析。安全配置管理涉及持续检测端点中各个组件之间的配置偏差和错误配置&#xff0c;并使它们重新对齐。 在本文中&#xff0c;将了解 Vulnera…

<Linux开发> linux应用开发-之-进程通信之管道例程

一、简介 所谓管道&#xff0c;是指用于连接一个读进程和一个写进程&#xff0c;以实现它们之间通信的共享文件&#xff0c;又称 pipe 文件。 向管道&#xff08;共享文件&#xff09;提供输入的发送进程&#xff08;即写进程&#xff09;&#xff0c;以字符流形式将大量的数…

Windows串口出厂测试工具与使用说明

WCHUsbSerTest是一款用于WCH USB转串口系列产品出厂测试的工具软件&#xff0c;方便用户对产品进行批量化功能测试。该软件支持以下特性&#xff1a; 支持设备热插拔检测&#xff0c;插入自动测试。支持两种测试模式&#xff1a;1个设备自测、2个设备互相连接测试。支持多种串…

VMware Horizon 8 2303 - 虚拟桌面基础架构 (VDI) 和应用软件

请访问原文链接&#xff1a;https://sysin.org/blog/vmware-horizon-8/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Version2303DocumentationRelease NotesRelease Date2023-03-30 虚拟桌面基础架构 (VDI) 和应用软件 VMw…

chagpt中文镜像版-ChatGPT工具下载

ChatGPT工具下载 ChatGPT是由OpenAI公司开发的预训练语言模型&#xff0c;目前已经开源并在GitHub上发布了相关代码和模型&#xff0c;提供了使用Python编写的API。如果您要使用ChatGPT&#xff0c;您可以通过以下步骤进行下载和安装&#xff1a; 在GitHub上下载&#xff1a;您…

【vue】 vue2 中使用 Tinymce 富文本编辑器

文章目录Tinymce 效果一、安装依赖二、封装组件-Tinymce.vue三、汉化文件-zh_CN.js四、vue使用组件封装五、整体目录结构六、可能会遇到的问题import "tinymce/icons/default" 路径找不到需要升级tinymce版本Tinymce 效果 一、安装依赖 npm i tinymce5.1.0 -S npm i…

计算机组成原理实验一(完整)

在VC中使用调试功能将下列语句运行的内存存放结果截图&#xff0c;每运行一句需截图一次。 #include<stdio.h> int main() {int a 你的学号末两位-100; //0x&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#x…

【Redis 进阶之路】3. Redis 主从 以及哨兵

Redis 主从 以及哨兵 单实例Redis 不足&#xff1a; 上述的是一个单实例的Redis。 我们可以先分析下有哪些不足&#xff1a; 单点故障 &#xff08;是每个单实例必须面对的问题&#xff09;容量有限 &#xff08;Redis毕竟是缓存型数据库&#xff0c;容量取决于服务器分配的容…