• 多线程编程核心技术(八)管程


    处理多线程可以从信号量和管程来进行。Linux就是使用信号量对进行多线程的。

    信号量是1965荷兰Dijkstra为了解决并发进程问题 而提出的一个重要操作系统的思想

    信号量是操作系统提供的一种协调共享资源访问的方法。和用软件实现的同步比较,软件同步是平等线程间的的一种同步协商机制,不能保证原子性。信号量则由操作系统进行管理,地位高于进程,操作系统保证信号量的原子性

    信号量是跟锁机制在同一个层次上的编程方法(那么原理是否可以认为是操作隔离性思想)。管程是为了解决信号量在临界区的PV操作上的配对的麻烦,把配对的PV操作集中在一起,生成的一种并发编程方法。

    两个或多个进程可以通过简单的信号进行合作,一个进程可以被迫在某个位置停止,直到它接收到一个特定的信号。任何复杂的合作需求都可以通过适当的信号结构 得到满足。为了发信号,需要使用一个称为信号量的特殊变量。为通过信号量s发送信号,进程可执行原语semSignal(s),即V操作;为了通过信号量 s接收信号,进程可执行原语semWait(s),即P操作;如果相应的信号还没有发送,则进程将被挂起,直至发送位置。

    信号量思想代码:

    semaphore empty=2; //定义empty对应盘子的剩余放水果的位置个数初值为2(      空缓冲区个数       ) 
        semaphore apple=0; //定义信号量apple对应盘子里的苹果数量初值为0 
        semaphore orange=0; //定义信号量orange对于盘子里的橘子数量初值为0 
        semaphore mutex=1: //定义信号量mutex来保护盘子被互斥地访问 
        father(){ //爸爸进程
        while(1){ 
            P(empty); //盘子的剩余放水果的位置减一,如果>=0,说明有位置可以放苹果
                P(mutex); 
                在盘子里放一个苹果 
                V(mutex); 
                V(apple);//盘中苹果数加一
        }
    }
    
    
    mother(){ //妈妈进程 
        while(1)  { 
            P(empty); //盘子的剩余放水果的位置减一,如果>=0,说明有位置可以放橘子
                P(mutex); //互斥变量减一,如果<0,则说明有进程在临界区。则当前进程必须等待。
                在盘子里放一个橘子
                V(mutex); 进程执行完毕,出了临界区,互斥变量值加一。
                V(orange); //盘中橘子数加一
        } 
    } 
    
    son(){ //用这段程序产生两个儿子进程 
        while(1)  { 
            P(orange); //盘中橘子个数减一,如果结果>=0时,说明盘中有橘子,可以取 
                P(mutex); 
                从盘子里拿一个橘子
                V(mutex); 
                V(empty); 取了一个橘子后,盘子的剩余放水果的位置加一
        }
    }
    daughter(){ //用这段程序产生两个女儿进程 
        while(1 )  { 
            P(apple);   //盘中苹果个数减一,如果结果>=0时,说明盘中有苹果,可以取
                P(mutex); 
                从盘子里拿出一个苹果 
                V(mutex);
                V(empty); 取了一个橘子后,盘子的剩余放水果的位置加一
    
        } 
    }

    管程可以被认为是一个建筑物,其中包含一个特殊的房间(下图的special room)。该特殊的房间在同一时间只能由一个客户(线程)占用,通常包含一些数据和代码。

     

     如果一个客户想要占据这个特殊房间,他必须先进入走廊(在上图是Hallway,在java中是Entry Set)等待。调度器将根据某些标准(例如FIFO先进先出)来选择一个客户。如果他出于某种原因中止,那么他将被送到等待室(上图wait room),并且在一段时间之后被安排重新进入特殊房间。正如上图所示,这座大楼里包含3间房间。

    那么如果信号量为1的情况下,和管程是一样的。

     在java内锁的实现是使用管程进行的,管程,对应的英文是 Monitor,所以也可以叫监视器

    在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。

    Hasen模型要求notify放到最后,这样T2线程通知T1后,T2线程就结束了,然后T1执行完,这样就能保证同一时刻只有一个线程在执行

    Hoare模型里面,T2线程通知完T1线程后,T2马上阻塞,T1马上执行;等T1执行完之后再唤醒T2线程,也能保证同一时刻只有一个线程在执行,但是T2多了一次阻塞唤醒操作

    MESA模型中,T2唤醒T1之后,T2还是会接着执行,T1并不立即执行,仅仅是从条件变量队列到等待队列中。

    这三个模型的共性就是控制了一个时刻只有一个线程can run。思考下为什么需要这样进化。

    Hasen的缺点我的理解是:可能会造成CPU放空炮,T1不满住执行条件,又需要重新呼叫T2。

    Hoare的缺点:如果T1无法执行。那么会浪费CPU部分资源,但是至少比哈森好,好在如果不满足,可以快速进行调度

    MESA的模型结合了上面两个的特点。缺点就是时刻不再固定,T1瞬息万变可能需要进行重新的条件判断——>乐观思想?

    多线程有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。

    同步是指线程之间在时间上的步调协调,并不一定会涉及到共享资源的互斥操作。比如线程1完成了自己的步骤1之后,要先等待线程2也完成了自己的步骤1,线程1才能进行自己的步骤2,这过程中线程1和线程2之间不一定有共享资源存在。
    就好比生活中的同学聚会,去聚会地点(步骤1),开跑车去的同学1先到达,步行去的同学2晚到达,但是同学1必须要等同学2也到达后(完成步骤1),才能进入聚会的吃饭环节(步骤2)。

    管程解决互斥问题的思路很简单,就是将共享变量及其对共享变量的操作统一封装起来(就像上面的小房子只能一个人)。假如我们要实现一个线程安全的阻塞队列,一个最直观的想法就是:将线程不安全的队列封装起来,对外提供线程安全的操作方法,例如入队操作和出队操作,不过实际上有点捞。

    管程其实更加像是面向对象的一种设计,你不安全是吧,那我就把你关起来,弄个小窗口和你交流。控制你的活动同时也规范了外部的活动。

    至于同步就有点困难,让每个条件变量都对应有一个等待队列,如下图,条件变量 A (A>100)和条件变量 B(C>200,A>1000) 分别都有自己的等待队列。

     当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。这个过程类似就医流程的分诊,只允许一个患者就诊,其他患者都在门口等待。

    Java 内置的管程方案(synchronized)使用简单,synchronized 关键字修饰的代码块,在编译期会自动生成相关加锁和解锁的代码,但是仅支持一个条件变量;而 Java SDK 并发包实现的管程支持多个条件变量,不过并发包里的锁,需要开发人员自己进行加锁和解锁操作。

    总结起来就是,管程就是一个对象监视器。任何线程想要访问该资源,就要排队进入监控范围。进入之后,接受检查,不符合条件(例如"火车进了隧道,发生全是汽车,那么就先让别的车先走"),则要继续等待,直到被通知,然后继续进入监视器。

  • 相关阅读:
    68
    56
    Django manager 命令笔记
    Django 执行 manage 命令方式
    Django 连接 Mysql (8.0.16) 失败
    Python django 安装 mysqlclient 失败
    H.264 SODB RBSP EBSP的区别
    FFmpeg—— Bitstream Filters 作用
    MySQL 远程连接问题 (Windows Server)
    MySQL 笔记
  • 原文地址:https://www.cnblogs.com/SmartCat994/p/14202094.html
Copyright © 2020-2023  润新知