管程
管程
一、是什么?
管程,是由一组数据以及定义在这组数据上的操作组成的模块或软件包。
管程的组成:
- 局部于管程内部的数据说明,对外而言是共享数据
- 对该数据结构进行操作的一组数据
- 对局部于管程的共享数据设置初始值的语句
管程的特性:
- 局部于管程的数据只能被管程内的过程访问
- 一个进程只有通过管程内的过程才能访问共享数据
- 每次仅允许一个进程在管程内执行某个过程
简单来说:
- 管程就是封装了临界区和互斥操作的实现,保证只有一个进程可以访问临界区
- 管程是一个编程语言成分,它不是由程序员来保证互斥访问,而是由编译器来实现
管程和信号量的区别是啥?
- 管程实际上是在信号量的基础上,再封装了一层,保证同步以及互斥
- 管程是一种高级同步原语,信号量则是一种低级同步原语
二、为什么?
引入管程的目的有:
- 为了解决临界区分散所带来的管理和控制问题
- 使用信号量控制临界区,每次都需要在临界区内写 P、V 操作,很容易就写错了
- 管程相当于把 P、V 操作移到了管程内部来统一实现,有效地避免了分散和难管理的问题
比如说,采用信号量访问临界区时:
1 | P(S) |
每次访问共享变量,就必须采用 P、V 操作,这样临界区就很分散(比如这里就分了3个临界区)。
如果在某个临界区,忘记写 P 或 V 操作了,必然会导致某种问题,比如并发不安全,永远阻塞等。
为了避免这种分散,不好管理,就出现了管程:
1 | monitor counter { |
注意,管程保证了同一时刻内只会有一个进程,所以没有并发问题。
管程,就是提供了很方便的接口给我们使用,并且还帮我们实现了同步和互斥。
三、怎么用?
管程是一种编程语言成分,也就是说,使用管程,必须是语言自身支持才行。
管程的互斥实现,是编译器负责的。
就比如说,java 中的 synchronized 关键字,实际上底层就利用了管程:
1 | synchronized increment (lock) { |
synchronized 关键字保护的临界区,可以保证只有1个线程能访问,底层实际上由管程对象来实现的。
访问 synchronized 的临界区时,必须先获取 lock 锁对象对应的管程对象。