操作系统概念(第六章) 进程同步(二)

进程同步

虽然信号量提供了一种方便且有效的机制以处理进程同步,但是使用不正确仍然会导致一些时序错误,且难以检测,因为这些错误只有在特殊的执行顺序的情况下才会出现,而这些顺序不会总出现。

管程(monitor)

管程是一种高级语言结构,它是编程语言的组成部分,编译器知道它们的特殊性,因此可以采用与其他过程调用不同的方法来处理对管程的调用。
进入管程时的互斥由编译器负责,但通常的做法是用一个互斥量或二元信号量。因为是由编译器而非程序员来安排互斥,所以出错的可能性要小得多。在任一时刻,写管程的人无须关心编译器是如何实现互斥的。他只需知道将所有的临界区转换成管程过程即可,决不会有两个进程同时执行临界区中的代码。

使用

管程类型提供了一组由程序员定义的、在管程内互斥的操作。管程类型的表示包括一组变量的声明(这些变量的值定义了一个类型实例的状态)和对这些变量操作的子程序和函数的实现。管程的类型表示不能直接为各个进程所使用。因此,在管程内定义的子程序只能访问位于管程内那些局部声明的变量和形式参数。类似的,管程的局部变量能被局部子程序访问。

管程结构确保一次只有一个进程能在管程内活动。不需要显示的编写同步代码。而对于特定同步方案,需要额外的同步机制,这些由条件(condition)结构来提供。

condition x,y;  
x.wait();   //调用操作的进程会被挂起
x.signal(); //重新启动一个悬挂的进程

管程的语法:

monitor monitor name{
    //shared variable declarations
    procedure P1(…){
    …
    }
    procedure P2(…){
    …
    }procedure Pn(…){
    …
    }
    initialization code(…){
    …
    }
}

现在假设当前操作x.signal()为一个进程P所调用,有一个悬挂进程Q与条件变量x相关联。显然,如果悬挂进程Q允许重新执行,那么进程P必须等待。否则,两个进程P和Q会同事在管程内执行。
然而注意到,从概念上说两个进程都可以继续操作。因此,有两种可能性存在:

  • 唤醒并等待:进程P等待直到Q离开管程,或者等待另一个条件。
  • 唤醒并继续:进程Q等待直到P离开管程,或者等待另一个条件。

对于选择1或选择2,都有合理的解释。由于P已经在管程中执行,所以选择2似乎更为合理。然而,如果允许进程P继续,那么Q所等待的逻辑条件在Q重新启动时可能已不再成立。

哲学家进餐问题的管程解法

这个解决方案要求哲学家在两只筷子都可以使用时才会拿起筷子。

为此,引入如下数据结构:

enum {THINKING, HUNGRY, EATTING} state[5];

加入条件,哲学家i只有在其两个邻居不再进餐时才能将变量state[i]设置为eating:

(state[(i+4)%5]!=eating)(state[i+1]%5!=eating)

哲学家i必须按以下顺序来调用操作
还需要声明
condition self[5]
其中哲学家i在饥饿且又不能拿到所需要的筷子时可以延迟自己。

现在可以描述哲学家进程问题的解答。筷子分布由管程dp来控制,管程dp的定义如下


monitor dp
{
    enum{THINKING, HUNGRY, EATING} state[5];
    condition self[5];

    void pickup(int i)
    {
        state[i] = HUNGRY;
        test(i);
        if(state[i] != EATING)
            self[i].wait();
    }

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

    void test(int i)
    {
        if( state[(i+4)%5] !=EATING && 
            state[(i+1]%5) !=EATING && 
            state[i] == HUNGRY )
        {
            state[i] == EATING;
            self[i].signal();
        }
    }

    init_code()
    {
        for(int i=0; i<5; i++)
            state[i] = THINKING;
    }
}

每个哲学家在用餐之前,必须调用操作pickup()。这可能挂起该哲学家进程。在成功完成该操作之后,该哲学家才可进餐。接着,他可调用操作putdown(),并开始思考。
哲学家i必须按以下顺序来调用操作

dp.pickup(i)
    ...
eat
    ...
dp.putdown(i)

基于信号量的管程实现

基于信号量的哲学家进餐问题的管程解法:每个管程都有一个信号量mutex(初始化为1),进程在进入管程之前,必须执行wait(mutex),在离开管程后必须执行signal(mutex)。
因为信号进程必须等待,直到重新启动的进程离开或等待,所以引入了另外一个信号量next(初始化为0)以供信号进程挂起自己。
另外还提供了整数变量next_count,以对挂起在next上的进程数量进行计数,因此,每个外部子程序F会替换成

wait(mutex);
    ...
body of F
    ...
if(next_count>0)
    signal(next);
else
    signal(mutex);

这样,确保了管程内的互斥。
条件变量的实现:对于每个条件变量x,引入信号量x_sem和整数变量x_count,两者均初始化为0。
x.wait()的实现:

x_count++;
if(next_count > 0)
  signal(next);
else
  signal(mutex);
wait(x_sem);
x_count--;

x.signal()的实现:

if(x_count>0)
{
  next_count++;
  signal(x_sem);
  wait(next);
  next_count--;
}

管程内的进程重启

等待最长的进程先重新运行。也可以使用条件等待构造。

x.wait(c);其中c表示优先值(priority number),会与悬挂进程的名称一起存储。

使用管程来管理资源时,为确保系统的正确,有两个条件是必须检查的:

第一,用户进程必须总是按正确顺序来对管程进行调用;

第二,必须确保一个不合作的进程不能简单地忽略由管程所提供的互斥关口,以及在不遵守协议的情况下直接访问共享资源。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值