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

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;
    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值