主要内容:信号量和管程
信号量
信号量的抽象数据类型
- 一个整形sem,具有两个原子操作
- p(),sem减一,如果小于0,则等待
- v(),sem+1,如果sem<=0,唤醒一个等待的进程
信号量是被保护的变量
- 初始化完成后,唯一改变一个信号量的值的方法是通过P V操作
- 操作必须时原子
P能够阻塞,V不会阻塞
信号量的使用场景
- 互斥
- 条件同步
信号量的使用
互斥
mutex = new Semaphore(1);
mutex->P();
...
mutex->V();
调度约束
condition = new Semaphore(0);
//Thread A
...
condition->P(); //等待线程B某一些指令完成之后再继续运行,在此阻塞
...
//Thread B
...
condition->V(); //信号量增加唤醒线程A
...
合作
生产者-消费者问题
正确性要求
- 在任何一个时间只能有一个线程操作缓冲区(互斥)
- 当缓冲区为空时,消费者必须等待生产者(调度,同步约束)
- 当缓存区满,生产者必须等待消费者(调度,同步约束)
class BoundedBuffer{
mutex = new Semaphore(1);
fullBuffers = new Semaphore(0); //说明缓冲区初始为空
emptyBuffers = new Semaphore(n); //同时可以有n个生产者来生产
};
BoundedBuffer::Deposit(c){
emptyBuffers->P();
mutex->P();
Add c to the buffer;
mutex->V();
fullBuffers->V();
}
BoundedBuffer::Remove(c){
fullBuffers->P();
mutex->P();
Remove c from buffer;
mutex->V();
emptyBuffers->V();
}
信号量实现
使用硬件原语
- 禁用中断
- 原子指令
class Semaphore{
int sem;
WaitQueue q;
};
Semaphore::P(){
--sem;
if(sem < 0){
Add this thread t to q;
block(p);
}
};
Semaphore::V(){
++sem;
if(sem <= 0){
Remove a thread t from q;
wakeup(t);
}
}
管程
目的: 分离互斥和条件同步的关注
管程可以理解为对于信号量的进一步封装,对一组信号量进行管理
管程在组成上划分为
- 一个锁
- 0或者多个信号量
使用方法:
- 收集在对象、模块中的相关共享数据
- 定义方法来访问共享数据
Lock
-
Lock::Acquire() 等待直到锁可用,然后抢占锁
-
Lock::Release() 释放锁
Condition Variable
- 允许等待状态进入临界区
- 允许处于等待(睡眠)的线程进入临界区
- 某个时刻原子释放锁进入睡眠
- Wait() operation
- 释放锁,睡眠,重新获得锁放回
- Signal() operation(or broadcast() operation)
- 唤醒等待者(或者所有等待者),如果有
管程实现生产者-消费者问题
信号量的实现
class Condition{
int numWaiting = 0;
WaitQueue q;
};
Condition::Wait(lock){
numWaiting++;
Add this thread t to q;
release(lock);
schedule(); //need mutex
require(lock);
}
Condition::Signal(){
if(numWaiting > 0){
Remove a thread t from q;
wakeup(t); //need mutex
numWaiting--;
}
}
生产者-消费者
class BoundedBuffer{
Lock lock;
int count = 0; //buffer 为空
Condition notFull, notEmpty;
};
BoundedBuffer::Deposit(c){
lock->Acquire(); //管程的定义:只有一个线程能够进入管程
while(count == n)
notFull.Wait(&lock); //释放前面的锁
Add c to the buffer;
count++;
notEmpty.Signal();
lock->Release();
}
BoundedBuffer::Remove(c){
lock->Acquire();
while(count == 0)
notEmpty.Wait(&lock);
Remove c from buffer;
count--;
notFull.Signal();
lock->Release();
}
经典同步问题
读者-写者问题
约束
- 允许同一时间有多个读者,但在任何时候只有一个写者
- 当没有写者时,读者才能访问数据
- 当没有读者和写者时,写者才能访问数据
- 在任何时候只能有一个线程可以操作共享变量
写者优先设计
一旦写者就绪,那么写者会尽可能的执行写操作.如果写者源源不断的出现的话,那么读者就始终处于阻塞状态.
//writer
Database::Write(){
Wait until readers/writers;
write database;
check out - wake up waiting readers/writers;
}
//reader
Database::Read(){
Wait until no writers;
read database;
check out - wake up waiting writers;
}
//管程实现
AR = 0; // # of active readers
AW = 0; // # of active writers
WR = 0; // # of waiting readers
WW = 0; // # of waiting writers
Condition okToRead;
Condition okToWrite;
Lock lock;
//writer
Public Database::Write(){
//Wait until no readers/writers;
StartWrite();
write database;
//check out - wake up waiting readers/writers;
DoneWrite();
}
Private Database::StartWrite(){
lock.Acquire();
while((AW + AR) > 0){
WW++;
okToWrite.wait(&lock);
WW--;
}
AW++;
lock.Release();
}
Private Database::DoneWrite(){
lock.Acquire();
AW--;
if(WW > 0){
okToWrite.signal();
}
else if(WR > 0){
okToRead.broadcast(); //唤醒所有reader
}
lock.Release();
}
//reader
Public Database::Read(){
//Wait until no writers;
StartRead();
read database;
//check out - wake up waiting writers;
DoneRead();
}
Private Database::StartRead(){
lock.Acquire();
while(AW + WW > 0){ //关注等待的writer,体现出写者优先
WR++;
okToRead.wait(&lock);
WR--;
}
AR++;
lock.Release();
}
private Database::DoneRead(){
lock.Acquire();
AR--;
if(AR == 0 && WW > 0){ //只有读者全部没有了,才需要唤醒
okToWrite.signal();
}
lock.Release();
}
哲学家就餐问题
信号量实现
#define N 5
#define LEFT (i + N - 1) % N // 左邻居
#define RIGHT (i + 1) % N // 右邻居
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef int semaphore;
int state[N]; // 跟踪每个哲学家的状态
semaphore mutex = 1; // 临界区的互斥,临界区是 state 数组,对其修改需要互斥
semaphore s[N]; // 每个哲学家一个信号量
void philosopher(int i) {
while(TRUE) {
think(i);
take_two(i);
eat(i);
put_two(i);
}
}
void take_two(int i) {
down(&mutex);
state[i] = HUNGRY;
check(i);
up(&mutex);
down(&s[i]); // 只有收到通知之后才可以开始吃,否则会一直等下去
}
void put_two(i) {
down(&mutex);
state[i] = THINKING;
check(LEFT); // 尝试通知左右邻居,自己吃完了,你们可以开始吃了
check(RIGHT);
up(&mutex);
}
void eat(int i) {
down(&mutex);
state[i] = EATING;
up(&mutex);
}
// 检查两个邻居是否都没有用餐,如果是的话,就 up(&s[i]),使得 down(&s[i]) 能够得到通知并继续执行
void check(i) {
if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {
state[i] = EATING;
up(&s[i]);
}
}