第十章 信号量与管程

一、信号量

  • 一个整形 sem(semaphore),两个原子操作;
  • P():sem 减1,如果 sem < 0,等待,否则继续;
  • V():sem 加1,如果 sem <= 0,唤醒一个等待的 P。

二、信号量的使用

1. 信号量的性质

  • 信号量是整数;
  • 信号量是被保护的变量,操作必须是原子操作;
  • P()能够阻塞,V()不会阻塞;
  • 假定信号量是公平的,使用 FIFO。

2. 信号量的分类

  • 二进制信号量:可以使 0 或 1
  • 一般/计数信号量:可取任何非负值

3. 信号量的应用

  • 用信号量实现临界区的互斥访问;
  • 用信号量实现条件同步。

4. 生产者-消费者问题

生产者
缓冲区
消费者

正确性要求

  • 在任何一个时间只能有一个线程操作缓冲区(互斥);
  • 当缓冲区为空,消费者必须等待生产者(条件同步);
  • 当缓冲区满,生产者必须等待消费者(条件同步)。

用信号量描述

  • 二进制信号量互斥;
  • 一般信号量 fullbuffers;
  • 一般信号量 emptybuffers。

信号量操作

Class BoundedBuffer {

    mutex = new Semaphore(1);

    fullBuffers = new Semaphore(0);

    emptyBuffers = new Semaphore(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);       

    }

}

  • 信号量的双用途:互斥和条件同步,但等待条件是独立的互斥
  • 读、开发代码比较困难;
  • 容易出错;
  • 不能够处理死锁问题。

四、管程

1. 管程的定义

  • 目的:分离互斥和条件同步的关注;
  • 管程:一个锁和0或者多个条件变量的组合,锁指的是临界区,条件变量是等待/通知信号量用于管理并发访问共享数据。
  • 一般方法:收集在对象/模块中的相关共享数据,定义方法访问共享数据。

2. 管程的组成

![[Pasted image 20220818221951.png]]

  • Lock::Acquire() - 等待直到锁可用,然后抢占锁;
  • Lock::ARelease() - 释放锁,唤醒等待者,如果有。

条件变量

  • 条件变量是管程内的等待机制;
  • wait()操作:将自己阻塞在等待队列中,唤醒一个等待者或释放管程的互斥访问;
  • signal()操作:将等待队列中的一个线程唤醒,如果队列为空,则等同空操作。

3. 管程的实现

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--;

    }

}

4. 用管程解决生产者-消费者问题

classBoundedBuffer {

    Lock lock;

    int count = 0;

    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();

}

5. 管程条件变量释放的处理方式

Hansen管程

  • 条件变量释放仅是一个提示,当前线程不会放弃管程访问;
  • 需要重新检查条件;
  • 高效。

![[Pasted image 20220818224534.png]]

Hoare 管程

  • 条件变量释放同时表示放弃管程访问;
  • 释放后条件变量的状态可用;
  • 低效。

6. 基本同步方法

![[Pasted image 20220818225342.png]]

五、读者-写者问题

1. 两种类型的使用者

  • 读者:不需要修改数据
  • 写者:读取和修改数据

2. 问题的约束

  • 允许同一时间有多个读者,但任何时候只有一个写者;
  • 当没有写者时,读者才能访问数据;
  • 当没有读者和写者时写者才能访问数据;
  • 在任何时候只能有一个线程可以操作共享变量。

3. 用信号量解决

共享数据

  • 数据集
  • 信号量 CountMutex 初始化为1
  • 信号量 WriteMutex 初始化为0
  • 整数 Rcount 初始化为0

读者优先

  • 写者
P(WriteMutex);
	write;
V(WriteMutex);
  • 读者
P(countMutex);
	if (Rcount == 0)
		P(WriteMutex);
	++Rcount;
V(CountMutex);

read;

P(countMutex);
	--Rcount;
	if (Rcount == 0)
		V(WriteMutex);
V(CountMutex);

4. 用管程解决

状态变量

AR = 0;       // # of active readers

AW = 0;           // # of active writers

WR = 0;      // # of waiting readers

WW = 0;           // # of waiting writers

Lock lock;

Condition okToRead;

Condition okToWrite;

读者

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) {

        WR++;

        okToRead.wait(&lock);

        WR--;

    }

    AR++;

    lock.Release();

}
Private Database::DoneRead() {

    lock.Acquire();

    AR--;

    if (AR ==0 && WW > 0) {

        okToWrite.signal();

    }

    lock.Release();

}

写者

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();

    }

    lock.Release();

}

六、哲学家就餐问题

1. 问题描述

  • 五个哲学家围绕一张圆桌而坐,桌子上放着五支叉子,每两个哲学家之间放一支;
  • 哲学家的动作包括思考和进餐,进餐时需要同时拿到左右两边的叉子,思考时将两只叉子放回原处。

2. 哲学家当前的状态

#dedine N 5                      // 哲学家的个数
#define LEFT i - 1 % N           // 第 i 个哲学家的左邻居
#define RIGHT (i+1)% N         // 第 i 个哲学家的右邻居
#define THINKING 0               // 思考状态
#define HUNGRY 1                 // 饥饿状态
#define EATING 2                 // 进餐状态
int state[N];                    // 记录每个人的状态

3. 信号量

semaphore mutex;                 // 互斥信号量,初值为1,控制对状态的访问
semaphore s[N];                  // 同步信号量,初值为0,是否要唤醒邻居

4. philosopher 函数

void philosopher(int i) {         // i 的值:0 到 N - 1
	
	while (True){                 // 封闭式循环
		
		think();                  // 思考中……
		
		take_forks(i);            // 拿到两把叉子或被阻塞
		
		eat();                    // 吃面条中……

		put_forks(i);             // 把两把叉子放回原处
	}
}

5. take_forks 函数

void take_forks(int i){           // i 的值:0 到 N - 1
	
	P(mutex);                     // 进入临界区

	state[i] = HUNGRY;            // 饥饿状态

	test_take_left_right_forks(i);  // 试图拿两把叉子

	V(mutex);                     // 退出临界区

	P(s[i]);                      // 没有叉子便阻塞
}

6. test_take_left_right_forks 函数

void test_take_left_right_forks(){ // i 的值:0 到 N - 1

	if (state[i] == HUNGRY &&
		 state[LEFT] != EATING &&
		 state[Right] != EATING)
	{
		 state[i] = EATING;        // 两把叉子到手
		 V(s[i]);                  // 提醒自己可以吃饭了
	}
}

7. put_forks 函数

void put_forks(int i){

	P(mutex);                      // 进入临界区

	state[i] = THINKING;           // 交出两把叉子

	test_take_left_right_forks(LEFT) // 看左邻居是否能够进餐

	test_take_left_right_forks(RIGHT) // 看右邻居是否能够进餐

	V(mutex);                      // 退出临界区
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值