管程

管程

一、是什么?

管程,是由一组数据以及定义在这组数据上的操作组成的模块或软件包。

管程的组成:

  • 局部于管程内部的数据说明,对外而言是共享数据
  • 对该数据结构进行操作的一组数据
  • 对局部于管程的共享数据设置初始值的语句

管程的特性:

  • 局部于管程的数据只能被管程内的过程访问
  • 一个进程只有通过管程内的过程才能访问共享数据
  • 每次仅允许一个进程在管程内执行某个过程

简单来说:

  • 管程就是封装了临界区和互斥操作的实现,保证只有一个进程可以访问临界区
  • 管程是一个编程语言成分,它不是由程序员来保证互斥访问,而是由编译器来实现

管程和信号量的区别是啥?

  • 管程实际上是在信号量的基础上,再封装了一层,保证同步以及互斥
  • 管程是一种高级同步原语,信号量则是一种低级同步原语

二、为什么?

引入管程的目的有:

  • 为了解决临界区分散所带来的管理和控制问题
  • 使用信号量控制临界区,每次都需要在临界区内写 P、V 操作,很容易就写错了
  • 管程相当于把 P、V 操作移到了管程内部来统一实现,有效地避免了分散和难管理的问题

比如说,采用信号量访问临界区时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
P(S)
count++;
V(S);

...

P(S)
count--;
V(S)

...

P(S)
count = 1;
V(S)

每次访问共享变量,就必须采用 P、V 操作,这样临界区就很分散(比如这里就分了3个临界区)。

如果在某个临界区,忘记写 P 或 V 操作了,必然会导致某种问题,比如并发不安全,永远阻塞等。

为了避免这种分散,不好管理,就出现了管程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
monitor counter {
int count;
void increment() {
count++;
}
void decrement() {
count--;
}
void set(int c) {
count = c;
}
}

counter.increment();
counter.decrement();
counter.set(1);

注意,管程保证了同一时刻内只会有一个进程,所以没有并发问题。

管程,就是提供了很方便的接口给我们使用,并且还帮我们实现了同步和互斥。

三、怎么用?

管程是一种编程语言成分,也就是说,使用管程,必须是语言自身支持才行。

管程的互斥实现,是编译器负责的。

就比如说,java 中的 synchronized 关键字,实际上底层就利用了管程:

1
2
3
synchronized increment (lock) {
count++;
}

synchronized 关键字保护的临界区,可以保证只有1个线程能访问,底层实际上由管程对象来实现的。

访问 synchronized 的临界区时,必须先获取 lock 锁对象对应的管程对象。

作者

jiaduo

发布于

2022-04-10

更新于

2023-07-16

许可协议