操作系统之进程互斥的经典问题的分析

简介: 基础了解的信息铺垫是关于使用mutex作为锁实现的核心,那就是原子操作P(wait)和V(singal)的作用及含义。 - P是操作就是使得信号量Semophore的数量减一,当然了前提是信号量的大小是大于0的,如果小于等于0,此进程就会阻塞在该信号量的等待队列上面,只有等待来自另外的进程的唤醒信息来唤醒它。

基础了解的信息


铺垫是关于使用mutex作为锁实现的核心,那就是原子操作P(wait)和V(singal)的作用及含义。
- P是操作就是使得信号量Semophore的数量减一,当然了前提是信号量的大小是大于0的,如果小于等于0,此进程就会阻塞在该信号量的等待队列上面,只有等待来自另外的进程的唤醒信息来唤醒它。
- V操作就是使得信号量的数量加一,而在此处信号量的如果是小于0的,那么这个数的值就是该信号量的等待队列的进程的数目。

在进行对信号量的操作的核心就是,只有P和V操作可以对信号量进行加减,其他操作没有此权限。

生产者消费者问题:


  • 首先是考虑生产者和消费者只有一对一,且仓库的容量也只有一的状况:
    生产者将生产的产品放入到仓库中。那么我们不难想象只有在仓库中有产品的时候,消费者才能消费;只有仓库存在空位的时候,生产者才能将生产的产品放入到仓库。此时,对于生产者而言仓库为empty就是它的资源,对于消费者而言,仓库为full就是它的资源。

    //对于生产者
    while(true){
        //生产产品
        p(empty);
        //将产品放入到仓库
        v(full);
    
    }
    --------------------------------------------------------------------------------------
    
    //对于消费者
    while(true){
        p(full);
        //消费产品
        v(empty);
    }
  • 现在考虑第二个阶段,那就是现实中仓库的容量n一般来说都是远远大于1的,那么情况会变成什么样呢?
    经过老师指导,使用环形缓冲原理就可以实现对仓库的良好的访问,而且效率也很高。

 //对于生产者
    i = 0;
    while(true){
        //生产产品
        p(empty);
        //将产品放入到仓库
        i = (i+1) % n ;
        v(full);
    }
    --------------------------------------------------------------------------------------
//对于消费者
    j = 0;
    while(true){
        p(full);
        //消费产品
        j = (j + 1 ) % n ;
        v(empty);
    }
  • 再回到现实中,往往一个仓库中产品是由好几家工厂来生产的,那么在向仓库中添加产品的时候不可避免的会发生冲突,这个时候添加一个锁机制是很有必要的,否则就有可能导致两家工厂添加产品的时候出现错误;同样对于消费者也应该进行同样的处理。
/对于生产者
    i = 0;
    while(true){
        //生产产品
        p(empty);
        p(mutex);
        //将产品放入到仓库
        i = (i+1) % n ;
        v(mutex);
        v(full);
    }
    --------------------------------------------------------------------------------------
//对于消费者
    j = 0;
    while(true){
        p(full);
        p(mutex);
        //消费产品
        j = (j + 1 ) % n ;
        v(mutex);
        v(empty);
    }

读者写者问题


对于这个经典的问题,我们需要了解的是读者与写者都是对于同一个Shared Data进行访问的,所以就有可能出现下面的几种情况。

1、读操作可以同时进行(R-R2、读操作和写操作不可以同时进行(R-W,互斥)
3、写操作和写操作不可以同时进行(W-W,互斥)

因此,我们需要谨慎的进行处理了。
- 下面是第一种我们可能想到的最朴素的想法。那就是加锁,不论三七二十一,双方都进行加锁,这样就不会出现上面的2,3问题了。但是却违背了1的问题,因为RR是可以并发的。

//对于读者
     while(true){
        p(mutex);
        //读取数据
        v(mutex);
     }

-----------------------------------------------------------------------------------------------
//对于写者
     while(true){
        p(mutex);
        //写入数据
        v(mutex);
     }

  • 所以我们要对读者进程进行进一步的修改。
    while (true)  {
         P(r_mutex);
         r_cnt++;
         if(r_cnt==1)  P(mutex);
         V(r_mutex);

         read();

         P(r_mutex);
         r_cnt- -;
         if(r_cnt==0) V(mutex);
         V(r_mutex);
     }

其中,里面的核心处理就在于

    P(r_mutex);
    r_cnt++;
    if(r_cnt==1)  P(mutex);
    V(r_mutex);

是对于r_cnt数量的修改时互斥的保证。实现的功能就是在所有读者中只允许有一个读者获得锁,这样便可以满足1,2,3的要求了。但是这并不是最完美的解决方案,因为有可能读者数量会很大很大,而写者根本没时间写入数据,导致“死等”的情况出现。因此我们还得进行进一步的改进措施。

  • 添加限制条件,即当写者获得锁的时候,所有读者进程将会阻塞。
    //核心就在于rw_mutex信号量的掌控上

    //读者
    while (true)  {
         P(rw_mutex);
         P(r_mutex);
         r_cnt++;
         if(r_cnt==1)  P(mutex);
         V(r_mutex);
         V(rw_mutex);
         read();
         P(r_mutex);
         r_cnt- -;
         if(r_cnt==0) V(mutex);
         V(r_mutex);
    }



    //写者
    while (true)  {
        P(rw_mutex);
        P(mutex);
        write();
        V(mutex);
        V(rw_mutex);
    }

哲学家吃米饭问题


rice.png
简单的介绍就是,哲学家竞争吃米饭,但是前提是拥有两只筷子。对于哲学家来说,筷子就是他们竞争的资源,现在的问题就是如何解决这一个互斥问题。
正常来讲,如果一个哲学家获得了一双筷子,那么旁边的哲学家就需要等待筷子的释放,之后才能获得资源。

    do{ 
          wait ( chopstick[i] );
         wait ( chopStick[ (i + 1) % 5] );
                 //  eat

         signal ( chopstick[i] );
         signal (chopstick[ (i + 1) % 5] );

                 //  think

    } while (TRUE);

但是这样会存在一个问题,那就是“死锁”,比如每个哲学家都拿到了一只筷子,那么整个代码就会处于Deadlock的处境。那么怎样才能解决这个问题呢?

下面是我自己的一些理解,可能不正确,欢迎大家批评指正
  • 对于奇数位哲学家,只有偶数位的哲学家可以首先拥有资源,这样就可以去除死锁,但是会有一只筷子剩余,这不符合对资源高效利用的原则;
  • 对于偶数位哲学家,奇数位与偶数位的可以轮流获取资源,这样也可以解决问题。
  • 使用随机的方式,让所有的哲学家随机的获取资源,这样有利有弊,稳定性可能稍微有点不太好。

总结


操作系统课上,胡燕老师幽默的讲课风格让我有了很大的热情来研究这里面的复杂的学问,虽然自己目前而言水平仍然很差,但是我从中收获到了很多。感谢老师!

目录
相关文章
|
19天前
|
算法 调度 UED
深入理解操作系统:进程调度与优先级队列
【10月更文挑战第31天】在计算机科学的广阔天地中,操作系统扮演着枢纽的角色,它不仅管理着硬件资源,还为应用程序提供了运行的环境。本文将深入浅出地探讨操作系统的核心概念之一——进程调度,以及如何通过优先级队列来优化资源分配。我们将从基础理论出发,逐步过渡到实际应用,最终以代码示例巩固知识点,旨在为读者揭开操作系统高效管理的神秘面纱。
|
12天前
|
消息中间件 安全 算法
深入理解操作系统:进程管理的艺术
【10月更文挑战第38天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是硬件与软件的桥梁,更是维持计算机运行秩序的守夜人。本文将带你走进操作系统的核心——进程管理,探索它是如何协调和优化资源的使用,确保系统的稳定与高效。我们将从进程的基本概念出发,逐步深入到进程调度、同步与通信,最后探讨进程安全的重要性。通过这篇文章,你将获得对操作系统进程管理的全新认识,为你的计算机科学之旅增添一份深刻的理解。
|
16天前
|
运维 JavaScript jenkins
鸿蒙5.0版开发:分析CppCrash(进程崩溃)
在HarmonyOS 5.0中,CppCrash指C/C++运行时崩溃,常见原因包括空指针、数组越界等。系统提供基于posix信号机制的异常检测能力,生成详细日志辅助定位。本文详解CppCrash分析方法,涵盖异常检测、问题定位思路及案例分析。
40 4
|
16天前
|
运维 监控 JavaScript
鸿蒙next版开发:分析JS Crash(进程崩溃)
在HarmonyOS 5.0中,JS Crash指未处理的JavaScript异常导致应用意外退出。本文详细介绍如何分析JS Crash,包括异常捕获、日志分析和典型案例,帮助开发者定位问题、修复错误,提升应用稳定性。通过DevEco Studio收集日志,结合HiChecker工具,有效解决JS Crash问题。
37 4
|
16天前
|
算法 调度 UED
深入理解操作系统:进程管理与调度策略
【10月更文挑战第34天】本文旨在探讨操作系统中至关重要的一环——进程管理及其调度策略。我们将从基础概念入手,逐步揭示进程的生命周期、状态转换以及调度算法的核心原理。文章将通过浅显易懂的语言和具体实例,引导读者理解操作系统如何高效地管理和调度进程,保证系统资源的合理分配和利用。无论你是初学者还是有一定经验的开发者,这篇文章都能为你提供新的视角和深入的理解。
38 3
|
18天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
20天前
|
算法 调度 UED
深入理解操作系统的进程调度机制
本文旨在探讨操作系统中至关重要的组成部分之一——进程调度机制。通过详细解析进程调度的概念、目的、类型以及实现方式,本文为读者提供了一个全面了解操作系统如何高效管理进程资源的视角。此外,文章还简要介绍了几种常见的进程调度算法,并分析了它们的优缺点,旨在帮助读者更好地理解操作系统内部的复杂性及其对系统性能的影响。
|
21天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
36 2
|
20天前
|
消息中间件 算法 Linux
深入理解操作系统之进程管理
【10月更文挑战第30天】在数字时代的浪潮中,操作系统作为计算机系统的核心,扮演着至关重要的角色。本文将深入浅出地探讨操作系统中的进程管理机制,从进程的概念入手,逐步解析进程的创建、调度、同步与通信等关键过程,并通过实际代码示例,揭示这些理论在Linux系统中的应用。文章旨在为读者提供一扇窥探操作系统深层工作机制的窗口,同时激发对计算科学深层次理解的兴趣和思考。
|
22天前
|
消息中间件 算法 调度
深入理解操作系统:进程管理与调度策略
【10月更文挑战第29天】本文将带领读者深入探讨操作系统中的核心组件之一——进程,并分析进程管理的重要性。我们将从进程的生命周期入手,逐步揭示进程状态转换、进程调度算法以及优先级调度等关键概念。通过理论讲解与代码演示相结合的方式,本文旨在为读者提供对进程调度机制的全面理解,从而帮助读者更好地掌握操作系统的精髓。
31 1
下一篇
无影云桌面