您的位置:首页 > 运维架构 > Linux

操作系统清华大学版笔记(十) 信号量、管程、条件互斥、经典同步问题(读者写者、哲学家问题)

2017-12-25 14:54 459 查看
前篇在此:

操作系统清华向勇陈渝版笔记(九) 同步协同多道程序设计和并发问题,同步互斥,死锁,临界区

操作系统(八)CPU调度 短剩余时间 吞吐量 轮循 实时调度 多处理器调度 (清华 向勇 陈渝版)

index

10-1 信号量

10-2 如何使用信号量

10-3 信号量实现细节

10-4 管程 条件变量

10-5 经典同步问题

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不会

思考:spinlock能否是FIFO?

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); //当前生产者可以往里面放多少个数据
}


如果Buffer不空,则full/empty设别的值

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)释放锁。唤醒后获得锁。

执行signal后,管程中有两个线程可以执行,应该选择哪个呢?





While和if的区别,前者release后是挂在条件变量上的多个线程抢CPU,抢到谁给谁,所以要check again, 后者是直接转交给了下一个线程,始终满足count< n的条件,所以直接走IF下面的语句

临界区和MONITOR比较:



两个同步互斥的机制,临界区和monitor,比起LOCK都可以解决更广泛的问题,但也都离不开底层的硬件支持。

同步互斥的不确定性强,调试困难。

同步结构

锁:互斥

条件变量:有条件的同步

其他原语:信号量

10-5 经典同步问题

读者-写者问题



读者只读,写者可以读取和写入。

读者优先,不按时间顺序,跳过等待的写者优先进行读操作。

信号量的实现:

Data是共享变量

整数Rcount 当前读者的数目初始化为0,当前写者只有一个

保证互斥 信号量countmutex读者 writemutex写者 初始化为1



writemutex操作保证了写的互斥,和当前没有读者时,读者进入的互斥。

Rcount保证了可以有多个读者,由于自身是共享变量(对多个读者而言),所以在++ /– /判断操作的前后要用到countmutex

利用管程实现读者写者问题:

写者优先

存在两类写者,正在操作的写者和在等待的,只有两类都不存在时读者才可以进入





哲学家就餐问题



用信号量解决



改进,如果右边叉子在,就吃,不然就等

if(fork((i+1)%N)){take_fork((i+1)%N);   break;}
else{put_fork(i);   wait_some/random_time();}


如果都等待的是相同的时间,5个人同步,那肯定更不行

如果是随机时间,可行。但不完美。可能会饥饿。







#define N 5
#define Left ((n+i-1)%n)
#define Right ((n+i+1)%n)












eating其实就是临界区

think 开始要置为thinking 且state是临界资源,要用P,V来保护

先用人脑子想伪代码,再想变量和特殊数据结构的变量,互斥还是同步?逐步细化。注意match

避免死锁,饥饿,低效。

后一篇:

操作系统清华大学版笔记(十一)死锁、死锁预防、死锁避免、死锁恢复、银行家算法和进程间通信(直接通信、间接通信)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐