说实话, 这章没有完全看懂
信号量
我们进入信号区可以如果是读操作, 那么就可以允许它几个信号同时进行, 如果是写操作 ,那么就设置只能是一个信号进行。
信号量的抽象数据类型
- 一个整形(sem),具有两个原子操作
- P(): sem减一,如果sem<0,等待,否则继续
- V(): sem加一,如果sem≤0,唤醒一个等待的P
P 和 V 都是两个科学家的名简称, 他们是提出这个概念的人。
信号量的使用
信号量是整数
信号量是被保护的变量
- 初始化完成后,唯一改变一个信号量的值的办法是通过P()和V()
- 操作必须是原子
P()能够阻塞,V()不会阻塞
我们假定信号量是公平的
- 没有线程被阻塞在P()仍然堵塞如果V()被无限频繁调用(在同一个信号量)
- 在实践中,FIFO经常被使用
两个类型信号量
- 二进制信号量: 可以是0或1
- 计数信号量: 可以取任何非负数
- 两者相互表现(给定一个可以实现另一个)
信号量可以用在2个方面
- 互斥
- 条件同步(调度约束——一个线程等待另一个线程的事情发生)
- 用二进制信号量实现的互斥
- 用二进制信号量实现的调度约束
condition = new Semaphore(0); //Thread A ... condition->P(); //等待线程B某一些指令完成之后再继续运行,在此阻塞 ... //Thread B ... condition->V(); //信号量增加唤醒线程A ...
一个线程等待另一个线程处理事务, 单单的互斥机制是不够的
- 正确性的要求
- 每个约束用一个单独的信号量。
- 二进程信号量的互斥
一般信号量 fullBuffers
一般信号量emptyBuffers
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; }; //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); } }
缺点:
1.信号量的双用途
- 互斥和条件同步
- 但等待条件是独立的互斥
1.读,开发代码比较困难
- 程序员必须非常精通信号量
1.容易出错
- 使用的信号量已经被另一个线程占用
- 忘记释放信号量
1.不能够处理死锁问题
管程
管程就可以解决上述信号量的缺点
定义及其目的
目的: 分离互斥和条件同步的关注
定义 :
是包含了一系列的共享变量及其争对这些变量的操作的函数的一个组合(模块)
- 一个锁: 指定临界区(确保互斥性)
- 0或者多个条件变量: 等待,通知信号量用于管程并发访问共享数据(用来挂起条件变量)
大致结构图:
一般方法
- 收集在对象,模块中的相关共享数据
- 定义方法来访问共享数据
1.Lock
- Lock::Acquire() 等待直到锁可用,然后抢占锁
- Lock::Release() 释放锁,唤醒等待者如果有
1.Condition Variable
- 允许等待状态进入临界区
- 允许处于等待(睡眠)的线程进入临界区
某个时刻原子释放锁进入睡眠
- Wait() operation
- 释放锁,睡眠,重新获得锁放回
- Signal() operation(or broadcast() operation)
- 唤醒等待者(或者所有等待者),如果有
实现
- 需要维持每个条件队列
- 线程等待的条件等待signal()