管程(Monitors)

管程(Monitors)

Posted by ZhaoLe on October 15, 2019
操作系统的互斥解决方案

临界区概念 临界区意味着这个区域是敏感的,因为一旦进程运行到这个区域,那么意味着会对公共数据区域或者文件进行操作,也意味着有可能有其它进程也正运行到了临界区。如果能够采用适当的方式,使得这两个进程不会同时处于临界区,那么就能避免竞态条件。

1. 信号量 semaphore 信号量出现是为了解决什么问题?

一个进程的阻塞和唤醒是在不同进程中造成的。进程A调用了sleep()进入睡眠,而进程B调用了weekup(A)就会把进程A给唤醒,然而,就在它调用sleep()方法之前,由于时钟中断,进程B开始运行。结果就是。由于进程A未进入阻塞阶段,因此调用wakeup()信号就丢失了,等到进程A从之前中断的位置继续开始运行调用sleep()并进入了阻塞,可能再也没有进程去唤醒它了。 因此,进程的阻塞和唤醒需要额外引入一个变量来进行记录,用变量记录唤醒的次数,每次被唤醒,变量的值加1,这样即使wakeup操作先与sleep,但是wakeup操作会被记录到变量中,当进程进行sleep时候,因为已经有其他进程 唤醒过,此时可以认为这个进程不需要进入堵塞状态。

这个变量在操作系统概念里,被称为信号量,对信号量有两种操作,down和up down对应sleep,它会先检查信号量是否大于0,如果大于,则减1,进程此时无需堵塞,相当于消耗掉一次wakeup,若信号量为0,进程则会进入堵塞状态

up对应着 wakeup,进行up操作后,如果发现进程进程堵塞在这个信号量上,那么系统会选择其中一个进程将其唤醒,此时信号量的值不需要变化,但是被堵塞的进程已经少了一个,如果up操作时没有进程堵塞在信号量上,那么它会将信号量加1.

2. 互斥量 mutex 互斥量是信号量的一种特例,它的值只有0和1,当我们不需要用到信号量的计数能力时候,我们可以使用互斥量,实际上就是同一时间只允许一个进程进入临界区,而信号量是允许多个进程同时进入。

Java互斥解决方案

管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。 管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。 更简单的说管程允许多线程互斥的访问共享变量。

1. 管程 monitor 使用semaphore和mutex需要小心的使用down和up操作。为了更容易的编写出好的并发程序,在mutex和semaphore的基础上,提出了管程(monitor).

操作系统层面本身并不支持管程机制,实际上,管程是属于编程语言的范畴,当要使用管程时,先了解一下语言本身是否支持,例如 C 语言它就不支持管程,Java语言支持管程。

使用 monitor 机制的目的主要是为了互斥进入临界区,为了做到能够阻塞无法进入临界区的进程/线程,还需要一个monitor object来协助,这个 monitor object 内部会有相应的数据结构,例如列表,来保存被阻塞的线程;同时由于 monitor 机制本质上是基于 mutex 这种基本原语的,所以 monitor object 还必须维护一个基于 mutex 的锁。 此外,为了在适当的时候能够阻塞和唤醒 进程/线程,还需要引入一个条件变量,这个条件变量用来决定什么时候是“适当的时候”,这个条件可以来自程序代码的逻辑,也可以是在 monitor object 的内部,总而言之,程序员对条件变量的定义有很大的自主性。不过,由于 monitor object 内部采用了数据结构来保存被阻塞的队列,因此它也必须对外提供两个 API 来让线程进入阻塞状态以及之后被唤醒,分别是 wait 和 notify。

synchronized关键字在使用的时候,往往需要指定一个对象与之关联,一种是修饰方法,一种是修饰方法块.

synchronized修饰的是方法,字节码中并没有monitorentry和monitorexit,而是会出现ACC_SYNCHRONIZED标记,JVM层面根据这个标志来区分一个方法是否同步。当存在这个标记时候,执行线程将先持有Monitor对象,然后再执行方法,在该方法运行期间,其他线程将无法获取到Monitor对象。当方法执行完后,再释放Monitor对象。

总之,synchronzied需要关联monitor object。管程序机制中,monitor object 充当着维护 mutex以及定义 wait/signal API 来管理线程的阻塞和唤醒的角色。

jvm中的同步是基于进入和退出monitor object实现的,每个实例都会有个monitor object ,可以和对象一起创建,销毁。

monitor实现的原理 java中管程是由ObjectMonitor实现,而ObjectMonitor是由c++编写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;      //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;   //持有monitor的线程
    _WaitSet      = NULL;   //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;  //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

68039c5b7a2ea8c75aa7766c157d2d86

多个线程访问一段同步去代码,多个线程会被存放在EntryList集合中,并处堵塞状态的线程都会在该表中,当某个线程获取了到对象的monitor时候,管程是依靠底层操作系统的Mutex lock来实现互斥,当申请成功,就持有mutex锁。如果有线程调用wait()方法 就会释放持有的mutex锁,并且该线程会进入WaitSet集合中,等到下一次呗唤醒,如果线程顺利执行完方法,也会释放mutex锁

参考文章