清华大学操作系统课程笔记:信号量和管程

本文已收录于 清华大学操作系统课程笔记 系列,共计 9 篇,本篇是第 7 篇

信号量和管程:都是操作系统提供的同步方法

今天来讲解 与 锁同级的一种抽象办法。——信号量

  • 信号量(Semaphore),有时被称为信号灯,是操作系统提供的,在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。
  • 两个进程协调谁能使用,由操作系统作为“裁判”。信号量的取值表示资源的数量
  • 信号量是早期操作系统的主要同步机制,现在很少用,但在计算机科学研究中还是非常重要的。

信号量

问:信号量与前面讲的临界区中中断、锁之类的有什么区别?

答:进程、线程、互斥锁与信号量之间的联系和区别 – 高志远的个人主页 (gaozhiyuan.net)

信号量是一个抽象的数据类型,由一个整型sem(取值表示资源的数目)和两个原子操作组成。

申请资源的时候用P(),释放资源用V()。

也可以类比成一个厨房里的三个锅等。

信号量的特性

信号量的优先级高于用户的进程优先级,保证原子性。

信号量跟自旋锁还有个区别是自旋锁没法做到线程之间的排队,而信号量可以排队。Ps:这里我们假设信号量是先进先出排队算法

信号量的实现

classSemaphore是一个数据类型,里面有sem表示资源数量,还有一个等待队列q。

P()表示申请资源,sem减去1。如果没取之前还剩下最后一个,减1后等于0——没关系,可以继续。如果原来就没了,0个, 减去1等于-1,就需要执行if语句了,陷入等待队列,进程放在等待队列中,并阻塞。

用完后释放:sem++,如果+1后还是小于等于0,说明其他进程一直在等着。就从等待队列中remove出一个线程放到就绪队列中(这里我们说了,就绪队列中默认用先进先出等待算法)

Ps:这里我们会发现很简单,是为什么呢?——因为这里的P和V操作都是原子的,不会被拆的指令破碎。好家伙,说白了操作系统互斥问题是把自己的活给CPU底层实现了

信号量的使用

用信号量实现临界区的互斥访问

用信号量实现临界区的条件同步

条件同步:希望B线程执行完X后才能让线程A执行N。——很正常的需求,有时候B是一个需要预先处理的任务,不执行完的话A拿到的东西就不是我们希望的。

用信号量实现生产者-消费者问题

可以把生产者看成打印作业程序、消费者看成打印机。同一时刻缓冲区要么写要么读,不可同时操作——这一部分可以参考CMU15 445 索引并发控制中的Latches的两种模式兼容性

BoundedBuffer结构体中的成员表示三个需求:

  • 在一个线程进行生产或消费时,其余线程不能进行生产或消费等操作,即保持线程间旳同步;
  • 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待;
  • 只有缓冲区不为空时,消费者才能从中取出产品,否则必须等待;

所以:

  • 互斥信号量mutex:实现进程对缓冲池的互斥使用
  • 信号量full:表示缓冲池中满缓冲区的数量,初始值为0
  • 信号量empty:表示缓冲池中空缓冲区的数量,初始值为n

下图是生产者消费者主要进行的操作:

实现互斥访问临界区:

现在,仅仅解决了互斥访问问题。下面还有条件同步需要解决。

下面这两句比较难理解,仔细读读~

左边:检查是否有空缓冲区,有了才能放一个资源,否则要等待。

右边:消费者取完一个资源后可以释放一个空缓冲区资源。

右边:消费者想取一个资源,要看里面是否有一个资源,没有的话要等待不能去取。

左边:生产者生产一个资源后就释放一个空缓冲区。

Ps:上面的empty、full缓冲区不好理解,你就理解成放置资源的盒子,如果生产了一个资源,就会锁定一个空盒子,释放一个满盒子。如果是消费者就是消费一个资源就是锁定一个空盒子,释放一个满盒子

使用信号量的缺点:读写代码困难、容易忘记释放信号量、不能有效避免死锁问题,需要在写程序的时候考虑到并处理。

管程——把P、V操作综合在一起

为了改进信号量处理临界区的麻烦。我们试图把P、V操作综合在一起,这就是管程。是一种并发程序的编程方法。管程并不由用户(程序)来实现,是由编程语言来实现。用户需要用管程时直接用现成的。

相当于把底层的同步和互斥抽象出来供程序员直接使用,其实就是封装的思想,对外提供接口。

管程的特点:

管程的思想:

条件变量

管程机制中的条件变量类似与信号量机制中的信号量。

条件变量的实现:

 

用条件变量实现生产者-消费者问题

但第二个行为确定性更好一些。

经典同步问题:哲学家就餐文题

五个人同时拿左边叉子。

,可以接受但性能不好。

继续改进:

经典同步问题:读者-写者问题

是计算机中的一种经典问题,经常存在于数据库等很多共享资源访问当中。

对共享数据的访问有读者(只能读,可以多线程同时读)和写者(可读可写,但只能有一个线程写)

右边。只能对第一个读者与写进行互斥,所以需要加一个判断。

只用最后一个离开的读者,才需要释放。

还要对Rcount本身进行读写互斥。关于读写互斥或者说读写兼容问题可以参考CMU数据库 索引并发控制 小节。

这种实现方式,是读者优先。

如何改成写者优先?因为我们希望读取的都是最新的数据。

还有其他优先策略吗?

有的,公平策略,有读者就不能有写者,有写者不能有读者。

下面用管程实现读者-写者问题:

while里的状态就决定了,是读者优先还是写者优先,还是公平的?

AW+WW:只要有写者,读者就一定要等待。——写者优先。

AR == 0:没有读者

WW > 0:有写者等着

——写者优先。

管程可以很好的实现优先策略。

作者: 高志远

高志远,24岁,男生

发表评论

邮箱地址不会被公开。