操作系统原理教程:管程

错用同步操作P(S)和V(S)时,同样会造成与时间有关的错误。

# P、V操作使用颠倒
P(mutex);                           V(mutex)
critical Section                    critical section
V(mutex);                           P(mutex)

# V操作出误用P操作
P(mutex);                           P(mutex)
critical Section                    critical section
V(mutex);                           P(mutex)

# 遗漏了P操作或V操作
管程的概念

一个管程定义了一个数据结构和在该数据结构上能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。管程在结构上由以下三部分组成:

  • 管程所管理的共享数据结构(变量),这些数据结构是对应临界资源的抽象。

  • 建立在该数据结构上的一组操作(函数)。

  • 对上述数据结构置初值的语句。

# 管程的语法描述

Monitor monitor_name            // monitor_name表示给管程起的名字,即管程名
{
    variable declarations       // 共享变量说明
    void entry P1(...)          // P1到Pn为一组过程(函数)
    {
        ...
    }
    void entry P2(...)
    {
        ...
    }
    ...
    void entry Pn(...)
    {
        ...
    }
    initialization code         // 对局部于管程的数据赋初值
}

管程管理的数据结构仅能由管程内定义的过程(函数)所访问,而不能由管程外的过程(函数)访问。管程中定义的函数又分为两种类型:一种是外部函数(带有标识符entry),另一种是内部函数(不带标识符entry)。外部函数是进程可以从外部调用的函数,而内部函数是只能由管程内的函数调用的函数。整个管程相当于一道“围墙”,它把共享变量所代表的资源和对它进行操作的若干函数围了起来,所有进程要访问临界资源,都必须经过管程这道“围墙”才能进入,而管程每次只准许一个进程进入,即便它们调用的是管程中不同的函数,以此自动实现临界资源在不同进程间的互斥使用。由于管程是一个语言成分,因此管程的互斥访问完全由编译程序在编译时自动添加,无须程序员关心,而且保证正确。

因为管程是互斥进入的,所以当一个进程试图进入一个已被占用的管程时,它应当在管程的入口处等待,因而在管程的入口处应当有一个进程等待队列,称为入口等待队列。在管程内部,如果进程P唤醒进程Q,则P等待Q继续,如果进程Q在执行又唤醒进程R,则Q等待R继续…这样,在管程内部,由于执行唤醒操作,可能会出现多个等待进程,因而还需要有一个进程等待队列,这个等待队列被称为紧急等待队列,它的优先级应当高于入口等待队列的优先级。

为了区别各种不同等待原因,在管程内设置了若干条件变量c1,c2,…,cn,局限于管程,并仅能从管程内进行访问;对于任一条件型变量cn,可以执行cwaitn和csignal操作。

  • cwaitn(c):如果紧急等待队列非空,则唤醒第一个等待者;否则释放管程的互斥权,本进程阻塞进入c变量的等待队列链。

  • csignal(c):如果c链非空,则什么都不做;否则唤醒第一个等待者,本进程的PCB进入紧急等待队列的尾部。

管程与进程的异同:

  • 二者都定义了数据结构。进程定义的是私有数据结构PCB;管程定义的是公共数据结构,如消息队列。

  • 二者都在各自的数据结构上进行有意义的操作。进程是由顺序程序执行有关操作,管程主要是进行同步操作和初启操作。

  • 二者设置的目的不同。进程是为了更好地实现系统的并发性而设置的,管程是为了解决进程的公共变量、共享资源的互斥使用问题而设置的。

  • 进程通过调用管程中的过程对共享变量实行操作。此时,该过程就如通常的子程序一样被调用而处于被动工作方式,因此,称管程为被动成分;与此相对应的进程则处于主动工作方式而被称为主动成分。

  • 由于进程是主动成分,故进程之间能被并发执行;然而管程是被动成分,管程和调用它的进程不能并发执行。

  • 进程可由“创建”而诞生,由“撤销”而消亡,有生命期;管程是操作系统中的固有成分,无须进程创建,也不能为进程所撤销,只能被进程调用。

管程解决生产者-消费者问题

首先要为它们建立一个管程,不妨命名为Producer-Consumer。其中包含以下两个外部函数:

  • put(item)函数。生产者进程利用该函数,将自己生产的“产品”放入到缓冲池中的某个空缓冲区内,并用变量count计数在缓冲池中已有的“产品”数量,当count=n,表示缓冲池已满,生产者需等待。

  • get(item)函数。消费者进程利用该过程从缓冲池中的某个缓冲区取得一个“产品”,当count<=0时,表示缓冲池已空,无“产品”可供消费,消费者应等待。

# PC管程的定义

Monitor PC;
    int in, out, count;
    item buffer[in];
    condition notfull, notempty;

    void entry put(item)
    {
        if count >= n then notfull.wait;            // 缓冲区满,进入等待队列
        buffer[in] = item;
        in = (in+1) mod n;
        count++;
        if notempty.queue then notempty.signal;     // 如果有进程因为缓冲区空而等待,唤醒它
    }

    void entry get(item)
    {
        if count <= 0 then notempty.wait;           // 缓冲区空,进入等待队列
        item = buffer[out];
        out = (out+1) mode n;
        count--;
        if notfull.queue then notfull.signal;       // 如果有进程因为缓冲区满而等待,唤醒它
    }

    {in = out = count = 0;}


cobegin
    void producer(int i)
    {
        while(true)
        {
            produce an item in nextp;
            PC.put(nextp);
        }
    }

    void consumer(int i)
    {
        while(true)
        {
            PC.get(nextc);
            consume the item in nextc;
        }
    }
coend
管程解决哲学家进餐问题

哲学家在不同的时刻可以处在以下三种不同的状态:进餐、饥饿和思考。为此,引入以下数组表示哲学家的状态:(thinking, hungry, eating) state[5];另外,还要为每一位哲学家设置一个条件变量self[i],每当哲学家饥饿,而又不能获得进餐所需的筷子时,可以通过执行self[i].wait来推迟自己的进餐。条件变量可描述为:condition self[5];在这两个数组的基础上,管程中共设置了以下三个函数:

  • entry pickup(i)函数(外部函数)。哲学家可利用该过程进餐。如某哲学家处于饥饿状态,且他的左、右两位哲学家都未进餐时,便允许这位哲学家进餐,因为他此时可以拿到左、右两根筷子;但只要其左、右两位哲学家中有一位正在进餐,便不允许该哲学家进餐,此时将执行self[i].wait操作来推迟该哲学家的进餐。

  • entry putdown(i)函数(外部函数)。当哲学家进餐完毕,通过执行该函数放下其手中的筷子,以便其左、右两边的哲学家可以竞争使用筷子进餐。

  • test(i)(内部函数)。该函数为测试函数,用来测试哲学家是否已具备用餐条件,即:state[(k+4)%5] != eating && state[k] == hungry && state[(k+1)%5] != eating条件为真。若为真,则允许该哲学家进餐,否则,该哲学家等待。该函数只能被本管程内的两个外部函数pickup()和putdown()调用,不能由进程之间调用。

Monitor Dining-Philosophers();
    condition state[5];
    condition self[5];

    void entry pickup(int i)
    {
        state[i] = hungry;
        test(i);
        if(state[i] != eating)
            self[i].wait;
    }

    void entry putdown(int i)
    {
        state[i] = thinking;
        test((i+4)%5);
        test((i+1)%5);
    }

    void test(int k)
    {
        if(state[(k+4)%5] != eating && state[k] == hungry && state[(k+1)%5] != eating)
        {
            state[k] = eating;
            self[k].signal;
        }
    }

    for(i = 0; i <= 4; i++)         // 管程初始化
    {
        state[i] = thinking;
    }

cobegin
    void philosopher(int i)
    {
        while(true)
        {
            thinking;
            pickup;
            eating;
            putdown
        }
    }

(最近更新:2019年09月18日)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值