您的位置:首页 > 其它

操作系统概念 --- 读书笔记 第6章:进程同步

2018-02-19 16:50 190 查看
6.1 临界区问题

假设有n个进程Pi,每一个进程都一个代码段称为临界区,在该区中可能改变共同变量、更新一个表、写一个文件等等,这个就是临界区。

临界区需要满足下面三项要求:

- 互斥(Mutual Exclusion):也即一个进程在临界区执行,其他的进程不允许进去执行。

- 前进(Progress):如果当前需要有进程去临界区执行,同时临界区没有进程在执行,可以选择一个进程去临界区执行,这个选择不可以无限推迟。

- 有限等待(Bounded Waiting):从一个进程请求进入临界区开始,到请求被允许结束,其他进程允许进入临界区的次数有上限,也即不可以无限的等待。

6.2 信号量 Semaphore

信号量多用于进程间的同步与互斥,简单说一下信号量的工作机制,可以直接理解成计数器(当然其实加锁的时候肯定不能这么简单,不只只是信号量了),信号量会有初值(>0),每当有进程申请使用信号量,通过一个P操作来对信号量进行-1操作,当计数器减到0的时候就说明没有资源了,其他进程要想访问就必须等待(具体怎么等还有说法,比如忙等待或者睡眠),当该进程执行完这段工作(我们称之为临界区)之后,就会执行V操作来对信号量进行+1操作。

这里有一个自旋锁的概念:

何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

6.3 经典同步问题

6.3.1 有限缓冲问题 (生产者消费者模型)

问题是这样的,在一个有限的缓冲池中,有生产者负责生产缓冲,有消费者负责消费缓冲。信号量mutex提供对缓冲池的互斥访问,信号量empty和full表示对空缓冲项和满缓冲项的个数,empty初始化为n,full初始化为0,那么生产者消费者模型如下:

下面是生产者代码

do {
...
//produce an new item
...

wait(empty);
wait(mutex);
...
//add en item to buffer
...
signal(mutex);
signal(full);

}while(true);


下面是消费者代码

do {

wait(full);
wait(mutex);
...
//remove en item to buffer
...
signal(mutex);
signal(empty);

...
//consume an  item
...
}while(true);


6.3.2 读者写者问题

问题是这样的,数据库的并发读没有问题,但是并发读写或者写写就存在数据不一致问题在,这个问题是这样解决的

信号量mutex和wrt均初始化为1,计数器readcount初始化为0,mutex用于更新readcount的互斥访问,readcount用于表示有多少个读者,wrt供写者作为互斥信号,它仅仅供第一个读者和最后一个读者使用,读者写者代码如下:

下面是写者代码:

do
{
wait(wrt);
...
//write
...
signal(wrt);
}while(true);


下面是读者代码

do
{
wait(mutex);
readcount++;
if(readcount==1)
wait(wrt);
signal(mutex);

...
//read
...

wait(mutex);
readcount--;
if(readcount==0)
signal(wrt);
signal(mutex);

}while(true);


6.3.3 哲学家进餐问题

问题描述是这样的,有5个哲学家围在一个圆桌吃饭,每两个哲学家之间有一根筷子,哲学家可以思考和吃饭,吃饭的时候必须使用邻近的两根筷子,吃完饭之后继续思考。

一个简单的解决方法就是每一根筷子使用一个信号量表示,chopstick[5],并且都初始化为1,那么对于第i个哲学家的代码如下:

do
{
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);

...
//eat
...

signal(chopstick[i]);
signal(chopstick[(i+1)%5]);

...
//think
...
}while(true);


这个答案虽然可以保证没有两个哲学家同时使用一根筷子,但是可能会造成死锁,比如所有的哲学家都拿起左边的筷子,所以这个答案应该舍弃,

下面是哲学家进餐问题的管程解决方案:

哲学界其实有三种状态,thinking、eating和hungry,所以对于5个哲学家有state[5]个状态表示,同时还有一个condition self[5]来表示哲学家饥饿而且拿不到筷子的时候可以延迟自己。

筷子分布使用管程dp来控制,每一个哲学家进餐之前必须调用操作pickup(),这个可能挂起哲学家进程,在成功完成该操作之后哲学家才可以进餐,接着就可以调用putdown(),并开始思考,如下

do
{
dp.pickup();
......
//eatubg
......
dp.putdown();
}while(true)


下面是使用信号量来实现管程,对于每一个管程都有一个信号量mutex(初始化为1)。进程在进入管程之前必须要执行wait(mutex),离开管程之后必须要执行signal(mutex)。

代码如下

monitor dp
{
enum {eat,hungry,think} state[5];
condition self[5];

void pickup(int i)
{
state[i]=hungry;
test(i);
if(state[i]!=eat)
self[i].wait();
}

void putdown(int i)
{
state[i]=think;
test((i+1)%5);
test((i+4)%5);
}

void test(int i)
{
if(state[(i+1)%5]!=eat && state[i]==hungry && state[(i+4)%5]!=eat)
{
state[i]=eat;
self[i].signal();
}
}

void init()
{
for(int i=0;i<5;i++)
state[i]=think;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: