信号量与管程

信号量与管程都是操作系统的并发编程机制,也是现在很多高级语言实现并发的一种底层原理。

信号量(Semaphore)

信号量机制是由大名鼎鼎的荷兰计算机科学家 Dijkstra 于1965 年提出的

操作系统的并发编程模型中,信号量(Semaphore)与锁机制(Mutex)一样都是对底层硬件同步方法的高级抽象
信号量与管程

信号量的模型

信号量模型的组成是这样的:

  • 两个成员变量:
    1. 整型数 count:用于记录共享资源数量
    2. 等待队列
  • 两个基本方法:
    1. P() 操作:将 count - 1,若 count < 0 时,线程进入等待队列(Prolaag 荷兰语,尝试减少)
    2. V() 操作:将 count + 1,若 count <= 0 时,唤醒一个等待队列中的线程(Verhoog 荷兰语,增加)

对信号量模型需要注意以下几点:

  • 初始化信号量时,我们指定 count 的值,之后 count 值的变化只能由 P() / V() 进行操作
  • 由操作系统实现并保证 P() / V() 操作的原子性
  • P() 操作是可能阻塞的,V() 操作不会阻塞
  • P() / V() 操作必须成对出现

信号量机制相比于锁机制的一个主要区别是:信号量机制允许多个线程同时访问一个临界区

因为只要 count 符合条件,那么线程就可以访问临界区资源

信号量机制也存在一些缺点,那就是真正使用起来比较困难,因为当我们需要解决的同步问题越复杂,条件越多,那么需要的信号量就越多,需要更加谨慎地处理信号量之间的处理顺序否则很容易造成死锁现象。

管程(Monitor)

管程是一种在信号量机制上进行改进的并发编程模型
信号量与管程

  • 它采用了面向对象的思想封装了线程间的同步控制
  • 在任意时刻最多允许一个线程执行管程代码
  • 管程也允许线程主动释放资源

管程模型

管程的组成如下:

  • 共享变量
  • 入口等待队列
  • 一个锁:控制整个管程代码的互斥访问
  • 0个或多个条件变量:每个条件变量都包含一个自己的等待队列,以及相应的出/入队操作

注意:在入口等待队列中的线程,都是条件满足情况未知曾经满足过条件的线程,它们等待管程的新一轮执行,而条件变量的等待队列中的线程必须等到满足该条件后才可以重新回到入口等待队列中去

管程模型相较于信号量模型的好处就是:易用,它可以把多个同步条件的操作都集中在一个模块里,从而简化同步机制的实现难度

管程的模型可以分为三类:

  • Hansen模型:要求唤醒线程操作放在代码最后,这样可以保证当前线程在唤醒其他线程时已完成自己的所有操作
  • Hoare模型:线程可以在代码任意位置执行唤醒操作,而且当唤醒其他线程后,自己立即阻塞,当被唤醒的线程完成操作后,再恢复自己
  • MESA模型:同Hoare一样,可以再代码任意位置执行唤醒操作,但被唤醒的线程不会立即执行,而是加入入口队列等待执行,因此在实现中需要将条件检查放入循环中,因为无法保证线程从唤醒到开始执行期间条件还是否保持满足

参考资料

  1. 操作系统,向勇,清华大学,第十八讲
  2. Java并发编程实战,王宝令,极客时间