[关闭]
@yanglt7 2018-05-25T15:11:39.000000Z 字数 6669 阅读 1429

第10章:信号量与管程

操作系统


10.1 信号量(Semaphore)

回顾

信号量

信号量的特性

信号量的实现

  1. ClassSemaphore {
  2. int sem;
  3. WaitQueue q;
  4. }
  5. Semaphore :: P() {
  6. sem --;
  7. if (sem<0) {
  8. Add this thread t to q;
  9. block(P);
  10. }
  11. }
  12. Semaphore :: V() {
  13. sem ++;
  14. if (sem<=0) {
  15. Remove a thread t from q;
  16. wakeup(t);
  17. }
  18. }

10.2 信号量的使用

信号量分类

信号量使用

用信号量实现临界区的互斥访问

  1. mutex = new Semaphore(1); //初始化
  2. mutex -> P(); //第一个线程进来之后,由1变成0,能进去;若第二个线程也想进入临界区,由0变成-1,在P操作进入等待状态
  3. critical section;
  4. mutex -> V(); //第一次线程执行结束,进行V操作,使信号量计数由-1变成0, 唤醒第二个进程,则第二个进程就会在第一个进程结束时进入临界区

用信号量实现条件同步

  1. condition = new Semaphore(0);
  2. //线程A 线程B
  3. ...M...
  4. condition -> P();
  5. ...N... //使用数据 ...X... //准备数据
  6. condition -> v();
  7. ...Y...

生产者-消费者问题

生产者 -> 缓冲区 -> 消费者

  1. CLass BoundedBuffer {
  2. mutex = new Semaphore(1);
  3. fullBuffers = new Semaphore(0);
  4. emptyBuffers = new Semaphore(n);
  5. }
  6. BoundedBuffer :: Deposit(c) {
  7. emptyBuffer -> P();
  8. mutex -> P();
  9. Add c to the buffer;
  10. mutex -> V();
  11. fullBuffers -> V();
  12. }
  13. BoundedBuffer :: Remove(c) {
  14. fullBuffer -> P();
  15. mutex -> P();
  16. Remove c from buffer;
  17. mutex -> V();
  18. emptyBuffers -> V();
  19. }

使用信号量的困难

10.3 管程(Monitor)


- 一个线程在临界区执行,必须执行到它退出临界区,它才能放弃临界区的互斥访问,而管程允许在执行的过程当中,临时放弃。放弃之后其他线程就可进入管程区域

管程的使用

管程组成

条件变量(Condition Variable)

条件变量实现

  1. Class Condition {
  2. int numWaiting = 0;
  3. WaitQueue = q;
  4. }
  5. Condition :: wait(lock) {
  6. numWaiting ++;
  7. Add this thread t to q;
  8. release(lock);
  9. schedule();//need mutex
  10. require(lock);
  11. }
  12. Condition :: Signal() {
  13. if (numWaiting>0) {
  14. Remove a thread from q;
  15. wakeup(t);//need mutex
  16. numWaiting --;
  17. }
  18. }

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

  1. ClassBoundedBuffer {
  2. ...
  3. Lock lock;
  4. int count = 0;
  5. Condition notFull,notEmpty;
  6. }
  7. BoundedBuffer :: Deposit(c) {
  8. lock -> Acquire();
  9. while (count == n)
  10. notFull.Wait(&lock);
  11. Add c to the buffer;
  12. count ++;
  13. notEmpty.Signal();
  14. lock -> Release();
  15. }
  16. BoundedBuffer :: Remove(c) {
  17. lock -> Acquire(c);
  18. while (count == 0)
  19. notEmpty.Wait(&lock);
  20. Remove c from buffer;
  21. count --;
  22. notFull.Signal();
  23. lock -> Release();
  24. }

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

  1. l.acquire()
  2. ...
  3. x.wait() //T1进入等待
  4. //T2进入管程 l.acquire()
  5. ...
  6. x.Signal()
  7. ...
  8. //T1退出管程 l.release()
  9. ... //T1恢复管程执行
  10. l.release()
  1. l.acquire()
  2. ...
  3. x.wait() //T1进入等待
  4. //T2进入管程 l.acquire()
  5. ...
  6. //T2进入等待 x.Signal()
  7. ... //T1恢复管程执行
  8. l.release() //T1结束
  9. ... //T2恢复管程执行
  10. l.release()

Hansen 管程和Hoare 管程

  1. Hansen-styleDeposit() {
  2. lock -> acquire();
  3. while (count == n) {
  4. notFull.Wait(&lock);
  5. }
  6. Add thing;
  7. count ++;
  8. notEmpty.Signal();
  9. lock -> Release();
  10. }
  11. Hoare-styleDeposit() {
  12. lock -> acquire();
  13. if (count == n) {
  14. notFull.Wait(&lock);
  15. }
  16. Add thing;
  17. count ++;
  18. notEmpty.Signal();
  19. lock -> Release();
  20. }

10.4 经典同步问题

(一)哲学家就餐问题

问题描述

方案1

  1. #define N 5 //哲学家个数
  2. semaphore fork[5]; //信号量初值为1
  3. void philosopher(int i) //哲学家编号:0-4
  4. while (TRUE)
  5. {
  6. think(); //哲学家在思考
  7. P(fork[i]); //去拿左边的叉子
  8. P(fork[(i+1)%N]); //去拿右边的叉子
  9. eat(); //吃面条中
  10. V(fork[i]); //放下左边的叉子
  11. V(fork[(i+1)%N]); //放下右边的叉子
  12. }

方案2

  1. #define N 5 //哲学家个数
  2. semaphore fork[5]; //信号量初值为1
  3. semaphore mutex; //互斥信号量,初值为1
  4. void philosopher(int i) //哲学家编号:0-4
  5. while (TRUE)
  6. {
  7. think(); //哲学家在思考
  8. P(mutex); //进入临界区,任一时刻只能有一个进程申请到二进制信号量
  9. P(fork[i]); //去拿左边的叉子
  10. P(fork[(i+1)%N]); //去拿右边的叉子
  11. eat(); //吃面条中
  12. V(fork[i]); //放下左边的叉子
  13. V(fork[(i+1)%N]); //放下右边的叉子
  14. V(mutex); //退出临界区
  15. }

方案3

  1. #define N 5 //哲学家个数
  2. semaphore fork[5]; //信号量初值为1
  3. void philosopher(int i) //哲学家编号:0-4
  4. while (TRUE)
  5. {
  6. think(); //哲学家在思考
  7. if (i%2 == 0) {
  8. P(fork[i]); //去拿左边的叉子
  9. P(fork[(i+1)%N]); //去拿右边的叉子
  10. } else {
  11. P(fork[(i+1)%N]); //去拿右边的叉子
  12. P(fork[i]); //去拿左边的叉子
  13. }
  14. eat(); //吃面条中
  15. V(fork[i]); //放下左边的叉子
  16. V(fork[(i+1)%N]); //放下右边的叉子
  17. }

(二)读者-写者问题

问题描述

用信号量解决读者-写者问题

  1. //Writer
  2. P(WriteMutex);
  3. write;
  4. V(WriteMutex);
  1. //Reader
  2. P(CountMutex);
  3. if(Rcount == 0) //第一个读者才需要申请读写互斥信号量,后来读者只需读者计数器加1
  4. P(WriteMutex);
  5. ++ Rcount;
  6. V(CountMutex);
  7. read;
  8. P(CountMutex);
  9. -- Rcount;
  10. if(Rcount == 0) //只有最后一个读者才需要释放读写互斥信号量、
  11. V(WriteMutex);
  12. V(CountMutex);

读者-写者问题:优先策略

用管程解决读者-写者问题

  1. Database :: Read() {
  2. Wait until no writers;
  3. read database;
  4. check out - wake up waiting writers;
  5. }
  6. Database :: Write() {
  7. Wait until no readers/writers;
  8. write database;
  9. check out - wake up waiting readers/writers;
  10. }
  1. AR=0; //# of active readers
  2. AW=0; //# of active writers
  3. // 只有一个>0
  4. WR=0; //# of waiting readers
  5. WW=0; //# of waiting writers
  6. //可能都>0
  7. Lock lock;
  8. Condition okToRead;
  9. Condition okToWrite;
  1. AR=0; //# of active readers
  2. AW=0; //# of active writers
  3. WR=0; //# of waiting readers
  4. WW=0; //# of waiting writers
  5. Lock lock;
  6. Condition okToRead;
  7. Condition okToWrite;
  1. Public Database :: Read() {
  2. //Wait until no writers;
  3. StartRead();
  4. read database;
  5. //check out - wake up waiting writers;
  6. DoneRead();
  7. }
  1. Private Database :: StartRead() {
  2. lock.Acquire();
  3. while ((AW+WW)>0) { //条件决定了优先策略
  4. WR++;
  5. okToRead.wait(&lock);
  6. WR--;
  7. }
  8. AR++;
  9. lock.Release();
  10. }
  1. Private Database :: DoneRead() {
  2. lock.Acquire();
  3. AR--;
  4. if ((AR==0 && WW)>0) { //条件决定了优先策略
  5. okToWrite.signal();
  6. }
  7. lock.Release();
  8. }
  1. AR=0; //# of active readers
  2. AW=0; //# of active writers
  3. WR=0; //# of waiting readers
  4. WW=0; //# of waiting writers
  5. Lock lock;
  6. Condition okToRead;
  7. Condition okToWrite;
  1. Public Database :: Write() {
  2. //Wait until no readers/writers;
  3. StartWrite();
  4. write database;
  5. //check out - wake up waiting readers/writers;
  6. DoneWrite();
  7. }
  1. Private Database :: StartWrite() {
  2. lock.Acquire();
  3. while ((AW+AR)>0) {
  4. WW++;
  5. okToWrite.wait(&lock);
  6. WW--;
  7. }
  8. AW++;
  9. lock.Release();
  10. }
  1. Private Database :: DoneWrite() {
  2. lock.Acquire();
  3. AW--;
  4. if (WW>0) {
  5. okToWrite.signal();
  6. } else if (WR>0) {
  7. okToRead.broadcast();
  8. }
  9. lock.Release();
  10. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注