信号量与管程也是进程间通信的方式。信号量是与锁在同一层级实现的,是操作系统提供的一种协调共享资源访问的方法。信号量由操作系统管理,操作系统作为管理者地位是高于进程的。
一、信号量
1、信号量(semaphore):是操作系统提供的一种协调共享资源访问的方法
①信号是一种抽象数据结构
- 一个整型int(sem),可进行两个原子操作
- P(): sem–,如果sem<0,等待,否则继续,类似lock_acquire
- V(): sem++,如果sem<=0,说明当前有等着的,唤醒挂在信号量上的进程,可以是一个,可以是多个
②信号量的特性
- 信号量是被保护的整数变量。初始化完成后,只能通过P()V()操作修改;由操作系统保证,PV操作时原子操作。
- P()可能阻塞,V()不会阻塞
- 通常假定信号量是公平的,线程不会无线阻塞在P操作,可以假定信号量等待时按照先进先出排队的。而自旋锁是无法实现先进先出的,因为它需要占用CPU资源不停查询锁是否空闲,无法指定进入临界区的顺序。
2、信号量的实现
二、信号量使用
1、信号量分两种类型:
- 二进制信号量:约等于锁,取值0 or 1
- 资源信号量:资源数目任何非负值
- 两者其实是等价的,可以基于一个构造出另一个
信号量的使用有两种情况:
- 互斥访问比如临界区的互斥访问控制、
- 条件同步比如线程间的条件等待。互斥访问如下图
3、生产者—消费者问题
生产者——>缓冲区——>消费者
①问题描述
- 一个或多个生产者在生成数据后放在一个缓冲区里;
- 单个消费者从缓冲区取出数据处理;
- 任何时刻只能有一个生产者或消费者可访问缓冲区
②问题分析:
- 任何时刻只能有一个线程操作缓冲区(互斥访问);
- 缓冲区空时,消费者必须等待生产者(条件同步);
- 缓冲区满时,生产者必须等待消费者(条件同步)
③信号量描述各个约束
- 二进制信号量mutex
- 资源信号量fullBuffers
- 资源信号量emptyBuffers
如下图是利用信号量实现的生产者消费者模型。生产者消费者模型的要求是同一时刻只能有一个生产者或消费者访问缓冲区,生产者只能在缓冲区有空间时才能往里塞数据,消费者只有缓冲区有数据时才能从里面消费数据。
三、管程
管程是一种用于多线程互斥访问共享资源的程序结构,采用面向对象的方法,简化线程间的同步控制,保证任意时刻最多只有一个线程执行管程代码,管程与临界区的区别是在管程中的线程可临时放弃管程的互斥访问,等待事件出现时恢复,而临界区只有线程退出临界区才能放弃互斥访问。
管程与临界区结构上差别在于多了共享数据,共享数据作为条件变量,如果条件变量的数量为0则跟临界区完全一样。进入管程的线程因资源被占用而进入等待状态,每个条件变量表示一种等待原因,对应着一个等待队列。管程最重要的两个操作:wait()和Signal(),wait将自身阻塞在等待队列中,唤醒一个等待者或释放管程的互斥访问,Signal操作将等待队列中的一个线程唤醒,如果等待队列为空,则等同空操作。
条件变量(condition variab)
- 条件变量是管程内的等待机制:进入管程的线程因资源被占用而进入等待状态;每个条件变量表示一种等待原因,对应一个等待队列;
- wait() 操作:将自己阻塞在等待队列中,唤醒一个等待者或释放管程的互斥访问;
- signal()操作:将自己队列中的一个线程唤醒;如果等待队列为空,则等同空操作;
调用管程解决消费者生产者问题
count记录了当前BUFFER的数据个数
先在前后加锁,因为要保证只有一个线程在临界区 lock在等待/睡眠的时候通过;lock->Acquire()管程进入和lock->Release()管程释放;notfull.wait(&lock)释放锁。唤醒后获得锁。
2、管程条件变量的释放处理方式
管程条件变量释放处理方式有两种如下图。可以看到Hoare的管程方式是更符合实际使用效果的,但是Hansen管程实现方式少了一次进程上下文切换,因此真实OS中一般使用Hansen管程方式。