zoukankan      html  css  js  c++  java
  • 多线程编程核心技术(八)管程

    处理多线程可以从信号量和管程来进行。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 并发包实现的管程支持多个条件变量,不过并发包里的锁,需要开发人员自己进行加锁和解锁操作。

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

  • 相关阅读:
    第五届“飞思卡尔”智能车竞赛分赛区赛后总结
    最佳编程语录
    通过UserAgent判断智能手机(设备,Android,IOS)
    Sql Server 系统存储过程分析 2 数据库引擎存储过程
    OutputCache 导致页面下载的解决办法
    w3wp.exe(IIS ) CPU 占用 100% 的常见原因及解决办法
    过滤并替换页面html输出
    Sql Server 系统存储过程分析 1 目录存储过程
    Sql Server 监控 Job 执行情况
    OutPutCache 自定义缓存:session、登录用户、cookie 等
  • 原文地址:https://www.cnblogs.com/SmartCat994/p/14202094.html
Copyright © 2011-2022 走看看