一、信号量
1、信号量(semaphore):是操作系统提供的一种协调共享资源访问的方法
①信号是一种抽象数据结构
一个整型int(sem),可进行两个原子操作
P() sem–,如果sem<0,等待,否则继续,类似lock_acquire
V() sem++,如果sem<=0,说明当前有等着的,唤醒挂在信号量上的进程,可以是一个,可以是多个
②信号量的特性
信号量是被保护的整数变量。初始化完成后,只能通过P()V()操作修改;由操作系统保证,PV操作时原子操作。
P()可能阻塞,V()不会阻塞
2、信号量的实现
二、信号量使用
1、信号量分两种类型:
①二进制信号量:约等于锁,取值0 or 1
②资源信号量:资源数目任何非负值
可以用在两个方面,互斥或者条件同步(调度约束—–一个线程等待另一个线程的事情发生)
③用二进制信号量实现锁的互斥
mutex= new semaphore(0) //设置一个信号量初值为0
mutex->P();
…Critical section…
mutex->V();
④必须成对使用P()和V()操作
P()操作保证互斥访问临界资源
V()操作在使用后释放临界资源
P()V()操作不能次序错误、重复或遗漏
2、信号量实现
class Semaphore{
int sem;
Waitqueue q;}
//P()操作
Semaphore::P(){
sem--;
if(sem<0){Add this thread t to q; block(p);}
}
//V()操作
Semaphore::V(){
Sem++;
If(sem<=0){
Remove a thread t from q; wakeup(t);}
}
存在问题:
- 信号量的双用途,互斥和条件同步,等待条件是独立的互斥。和LOCK有区别,LOCK是通过忙等/等待队列实现sleep,信号量是等待队列。
- 开发容易犯错,比较困难
- 不能够处理死锁
3、生产者—消费者问题
生产者——>缓冲区——>消费者
①问题描述:一个或多个生产者在生成数据后放在一个缓冲区里;单个消费者从缓冲区取出数据处理;任何时刻只能有一个生产者或消费者可访问缓冲区
②问题分析:任何时刻只能有一个线程操作缓冲区(互斥访问);缓冲区空时,消费者必须等待生产者(条件同步);缓冲区满时,生产者必须等待消费者(条件同步)
③信号量描述各个约束
二进制信号量mutex
资源信号量fullBuffers
资源信号量emptyBuffers
Ⅰ、初始化:
Class BoundedBuffer{
Mutex = new semaphore(1);
fullBuffers = new semaphore(0);
emptyBuffers = new semaphore(n); //当前生产者可以往里面放多少个数据
}
Ⅱ、如果Buffer不空,则full/empty设别的值
BoundedBuffer::Deposit(c){
emptyBuffer->P(); //n个生产者都可以进入直到empty<0被阻塞,不能先锁再emptybuffer--, 不然会在Buffers满的时候死锁
mutex->P();
Add c to the buffer;
mutex->V();
fullBuffers->V(); //初始是0,只有先V()不然消费者不能取}
BoundedBuffer::Remove(c){
fullBuffers->P(); // 初始是0时会被挂起
mutex->P();
Remove c from buffer;
mutex->V();
emptyBuffers->V();
}
//P&V可以换顺序吗?可以。
三、管程
1、管程monitor
①、定义:包含了一系列的共享变量,以及针对这些变量的操作的函数的组合/模块 包含了:一个锁,指定临界区,确保互斥性;0或者多个条件变量,根据条件的个数决定,等待/通知信号量,并发访问共享数据
②、条件变量(condition variab)
Ⅰ、条件变量是管程内的等待机制:进入管程的线程因资源被占用而进入等待状态;每个条件变量表示一种等待原因,对应一个等待队列;
Ⅱ、wait() 操作:将自己阻塞在等待队列中,唤醒一个等待者或释放管程的互斥访问;
Ⅲ、signal()操作:将自己队列中的一个线程唤醒;如果等待队列为空,则等同空操作;
↑ schedule选择下一个process区执行
调用管程解决消费者生产者问题
count记录了当前BUFFER的数据个数
先在前后加锁,因为要保证只有一个线程在临界区 lock在等待/睡眠的时候通过;lock->Acquire()管程进入和lock->Release()管程释放;notfull.wait(&lock)释放锁。唤醒后获得锁。
2、管程条件变量的释放处理方式