清华大学操作系统OS学习(十二)——信号量与管程

一、信号量

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、管程条件变量的释放处理方式

猜你喜欢

转载自blog.csdn.net/qq_36552489/article/details/92801443