10-1 信号量 Semaphore
一个整型int(sem),可进行两个原子操作
P() sem–,如果sem<0,等待,否则继续,类似lock_acquire
V() sem++,如果sem<=0,说明当前有等着的,唤醒挂在信号量上的进程,可以是一个,可以是多个
Dijkstra在20实际60年代提出,P,V都是荷兰语,在早期操作系统中,P,V是主要的同步原语。
类似信号灯
信号量是整数,有符号,一般初始是>0的数,一旦小于0就不能继续,要挂在信号量上,其他进程做V操作才能唤醒,具体唤醒哪个取决于具体的算法,如常用FIFO先来先服务,较为公平;
信号量是被保护的变量,初始化完成后只能通过P() V()这两个原子操作改变值,P操作会阻塞,V不会
10-2 如何使用信号量
两种类型:
二进制信号量:约等于锁,取值0 or 1
general counting一般/技术信号量:任何非负值
可以用在两个方面,互斥或者条件同步(调度约束—–一个线程等待另一个线程的事情发生)
用二进制信号量实现锁的互斥
mutex= new semaphore(0)
mutex->P();
…Critical section…
mutex->V();
实现调度约束
condition=new semaphore(0);
for thread A condition->P(); 开始等待for thread B
condition->V(); 发出信号唤醒A ,B在V操作之前的语句执行完后才能执行A的P之后的语句----同步
条件同步
正确性要求:可以多个生产者访问Buffer写数据,但写的时候消费者不能有操作(互斥),缓冲区空,
消费者必须等待生产者,缓冲区满,生产者必须等待消费者(调度/同步约束)。
每个约束用一个单独的信号量,设置三个
二进制信号量互斥(1/0),一般信号量fullbuffers,一般信号量emptybuffer
初始化
Class BoundedBuffer{
Mutex = new semaphore(1);
fullBuffers = new semaphore(0);
emptyBuffers = new semaphore(n); //当前生产者可以往里面放多少个数据
}
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可以换顺序吗?可以。
10-3 信号量实现细节
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);}
}
存在问题:
- 信号量的双用途,互斥和条件同步,等待条件是独立的互斥。和LOCK有区别,
- LOCK是通过忙等/等待队列实现sleep,信号量是等待队列。
- 开发容易犯错,比较困难
- 不能够处理死锁
10-4 管程 条件变量
管程monitor 包含了一系列的共享变量,以及针对这些变量的操作的函数的组合/模块
包含了:一个锁,指定临界区,确保互斥性;0或者多个条件变量,根据条件的个数决定,等待/通知信号量,并发访问共享数据
lock acquire & release 可以写函数也可以是语言级的
condition variable 条件变量
wait() 释放锁,睡眠
signal / broadcast 唤醒等待者
类似信号量 要维持每一个条件队列
num waiting与信号量有差异,是等的队列的数目;signal不一定会使其减一
count记录了当前BUFFER的数据个数
先在前后加锁,因为要保证只有一个线程在临界区 lock在等待/睡眠的时候通过notfull.wait(&lock)释放锁。唤醒后获得锁。
未完。。。。。。。。。。