这一节是操作系统进程同步问题的最后一节,主要总结一下关于管程的相关概念。关于进程同步问题的其他解决方法可参见:
操作系统笔记:(0)学堂在线Thu MOOC笔记目录 中的八,九讲。
我们依然通过一下几部分来讲解管程:
- 管程引入
- 条件变量
- Hansan管程与Hoare管程
- 管程解决生产者消费者问题
- java中的管程
- 管程解决哲学家就餐问题
- 信号量与管程的比较
管程引入
首先回顾一下上一篇blog中的进程同步解决图
信号量作为高层抽象来解决了绝大部分的进程同步问题。可是信号量有一个比较大的问题是成对的P、V操作,如果应用程序员使用的不合理的话很容易出现进程同步问题(死锁,饥饿),特别是在上一篇中的信号量解决生产者消费者问题。如果P/V配对配的不好的话很可能出现问题,而且对程序员的要求也很高。
所以提出了 管程 的概念,这是一种比信号量更高的抽象。或者说并没有这种实体存在于系统或编程语言中,更多的是一种机制,一种解决方法,但是编程语言和操作系统都提供了实现管程的重要部件 条件变量
管程定义
管程就是管理一组共享变量的程序。主要有一下几部分组成
mutex // 一个互斥锁,任何线程访问都必须先获得mutex
condition //一个或者多个,每个条件变量都包含一个等待队列
//共享变量及其访问方法
他有以下特点:
- 采用面向对象方法,简化线程同步
- 同一时刻仅有一个线程在管程中工作
- 可临时放弃管程的访问权,叫醒一个在等待队列中的线程,这是其他方法都没有的(原子锁和信号量一旦进入临界区就必须执行完临界区代码才能退出),而实现这一点采用的就是条件变量
条件变量
条件变量是一个类似于信号量。它由一个整形变量和一个等待队列组成,同时包含两个操作:
- wait
- signal
这里给出 THU MOOC中的实现
remark
这里的wait和P操作是不一样的,最大的不同就是他一定会将自己阻塞同时释放锁。而signal如果没有等待线程相当于一个空操作。而且numWaiting 不可能为负数
管程解决生产者消费者问题
我们先简单的给出伪代码,在后面会给出java代码,我们可以看到,最大的不同就是可在临界区临时放弃访问权,将自己阻塞。
Hansan管程与Hoare管程
这两种管程的区别简单来说就是从等待队列中唤醒的时候,一个用while一个用if。具体来说
第一种,Hansen管程他是让在管程中的线程更优先。而Hoare管程是让在等待队列中的更优先。我们可以看到第一种仅需切换两次,而第二种会切换三次,开销会更大,所以也就是在教科书中用一用。
我们看一看两种管程对生产者的实现
可以看到看到两种管程的区别就在while 和if
while会再次判断条件,也就使得在管程中的线程更优先,而if会是在等待队列中的线程更优先。
最后简单谈一下java中的管程和哲学家就餐问题的管程解决方案。
java中的管程
关于Java的管程我了解的不多,或者说关于java 的并发我并没有深入的了解。我这里简单的给出几点和管程相关的理解
- java 中每个对象都有一个内置锁,所以可以更简单的实现管程,不过要添加条件变量需要设置一个可重入锁
ReentrantLock()
- 由于每个对象都存在内置锁,所以可以简单地在方法前面添加
synchronized
关键字实现管程中线程同步,所有用synchronized
关键字修饰的方法都是默认管程操作方式的,即统一时刻仅有一个线程可访问synchronized
方法 - 条件变量获取方式
Condition notFull = lock.newCondition();
关于他的更多内容可参见:
这里有一个用管程解决 生产者消费者 问题的例子 (code 来源于官方文档)
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
哲学家就餐问题
信号量与管程的比较
信号量与管程本质上是互通的,也即信号量能解决的同步问题,管程也能解决,这一点在Monitors: An Operating System Structuring Concept 中得到了证明。需要注意的是信号量并不能解决所有进程同步问题,这一点可在书籍操作系统概念
中找到。下面说说两者的区别:
- 信号量本质是可共享的资源的数量; 而管程是一种抽象数据结构用来限制同一时刻只有一个线程进入临界区
- 信号量可以是并发的,而管程内部一次只能有一个进程访问
- 信号量的P操作可能阻塞,也可能不阻塞;而管程的wait操作一定会阻塞
- 信号量的V操作如果唤醒了其他线程,当前线程与被唤醒线程并发执行;对于管程的signal操作,要么当前线程继续执行(Hansen),要么被唤醒线程继续执行(Hoare),二者不能并发。
以上内容参考并发与同步、信号量与管程、生产者消费者问题