浅谈操作系统-进程同步

进程互斥

互斥的概念

临界区(critical section):不允许多个并发进程交叉执行的一段程序
互斥:不允许两个以上的共享该资源的并发进程同时进入临界区。

一个好的互斥方案应满足以下条件:

  1. 不能假设并发进程的相对执行速度。也就是说各并发程序享有平等、独立的竞争资源的权力。
  2. 临界区外的进程不应阻止其他进程进去临界区
  3. 不允许两个进程同时处于临界区
  4. 临界区的代码执行时间要短

在这里,前三个准则是保证并发程序进程享有平等的机会占有资源。
最后一个准则是为了防止死锁的产生。

实现互斥的方案

1.加锁实现

设置共享变量lock
lock=0 临界区内无进程
lock=1 临界区内有进程

while(lock);
lock = 1;
<Critical Section>
lock = 0;
<NoCritical Section>

但是加锁实现的方法是有缺陷的:当上锁的时候,lock=1执行之前的时候,可能已经有多个进程进去临界区。

2.严格轮转法

设置共享变量turn,指示进入临界区的进程号
以2个进程为例
turn=0 允许进程0进入临界区
turn=1 允许进程1进入临界区

进程0:

while(turn != 0);
;
<Critical Section>
lock = 1;
<NoCritical Section>

进程1:

while(turn != 1);
;
<Critical Section>
lock = 0;
<NoCritical Section>

很显然,这不是一个现实的方案

3.Peterson解决方法

enter_region(process);
<Critical Section>
leave_region(process);
<NoCritical Section>

当一个进程想进入临界区的时候,先调用enter_region判断是否能够安全进入,不能的话等待。退出的再调用leave_region允许其他进程进入临界区。
两个函数的参数都是进程号。

#define FALSE 0;
#define TRUE 1;
#define N 2//进程的个数
int turn;//轮到谁
int interested[N];//兴趣数组,初始值都为FALSE

/*
*turn表示要进入临界区的进程号
*interested[i] == TRUE 表示进程i要求进入或者正在临界区执行
*/
void enter_region(int process){
    int other;
    other = 1 - process;
    interested[process] = TRUE;//希望进入临界区
    turn = process; //设置标志位
    while(turn == process && interested[other] == TRUE);
}

void leave_region(int process){
    interested[process] == FALSE;
}

4.关中断

因为CPU在中断的时候才会发生进程切换
所以进程要进入临界区关中断,离开了再开中断就可以实现

关中断;
<Critical Section>
开中断;
<NoCritical Section>

缺点:
1. 对于多处理机系统无效。在多处理机系统中,可能存在一个以上的进程在不同的处理机上同时执行,关中断不能保证互斥。
2. 将关中断的操作交给用户很危险。

5.机器指令

TSL指令:执行TSL指令的CPU锁住总线,以禁止其他的CPU在本指令结束之前访问内存

前面的方法虽然能够实现互斥,但是存在着忙等待的问题
当一个进程想进入临界区的时候,要不断的检测是否能够进入,这样会浪费大量的CPU时间去等待,还会有优先级反转的问题

信号量

定义

下面让我们仔细的思考一下为什么会出现忙等待的情况:
首先,每个进程自己维护自己的一个测试,去判断自己能否进入临界区。这样就导致了每个进程能否进入临界区依靠自己的测试去判断,这样没有获得执行机会的进程当然无法判断,出现不公平的现象。而获得执行机会的进程又要不断地去测试,占用了CPU的执行时间。
如何解决问题,最主要的是让一个超越应用进程的管理者负责如何分配资源,这个管理者就是信号量

信号量是一个整数。
在sem>=0的时候代表可供并发进程使用的资源实体数;
sem<0表示正在等待使用临界区的进程数

信号量的使用:
必须置一次并且只能置一次初值
只能用PV操作信号量
除此之外不能使用其他方式访问信号量

PV操作

P、V操作是原子操作(不可分割)
信号量的数据仅能由PV原语操作改变
原语的两方面的含义:
1. 机器指令级:原语的程序段在执行过程中不允许中断
2. 功能级:原语的程序不允许并发执行

P操作的主要动作:

  1. sem-1
  2. 若sem>=0,p原语返回,进程继续执行
  3. 若sem<0,进程阻塞,并插入到该信号量的等待队列中。之后进程调度

V操作的主要动作:

  1. sem+1
  2. 若sem>0,V原语停止执行,返回调用初继续执行
  3. 若sem<=0,从等待队列中唤醒一个进程,然后在返回或者将进程调度

信号量的实现方式

忙等待

//信号量类型定义
typedef struct{
    int value;//信号量的值
    int lock;//锁,初始值为0
}Semophore_t;

//V操作
void V(Semophore_t *ps){
    while(TSL(&ps->lock));//上锁
    ps->value++;
    ps-lock=0;
}

//P操作
void P(Semophore_t *ps){
    for(;;){
        while(TSL(&ps->lock));//上锁
        if(ps->value > 0){
            ps->value--;
            break;
        }
        ps->lock = 0;
    }
    ps->lock = 0;
}

这里P操作这样写是为了当信号量的值小于的时候立马放开锁

阻塞

//信号量类型定义
typedef struct{
    int value;//信号量的值
    SemaQueue *list; //等待该信号量的等待队列
    int lock;//锁,初始值为0
}Semophore_t;


//P操作
void P(Semophore_t *ps){

        while(TSL(&ps->lock));//上锁
        if(ps->value > 0){
            ps->value--;
            ps->lock = 0;
        }else{
            将该进程加入ps->list;
            阻塞该进程;
            ps->lock = 0;
        }
}

//V操作
void V(Semophore_t *ps){
    while(TSL(&ps->lock));//上锁
    if(ps->list == NULL){
        ps->value++;
    }else{
        从ps->list移除一个进程;
        将该进程放入就绪队列;
    }
    ps-lock=0;
}

进程同步

把异步环境下的一组并发进程,因直接制约而互相发送消息而进行互相合作、互相等待,使得各进程按一定的速度执行的过程就做进程同步

生产者消费者问题

#define N 100
#define TRUE 1

//信号量定义
Semaphore_t mutex = 1,//保证进程互斥访问缓冲区
empty = N,//空槽的个数
full = 0;//非空槽的个数

void producer(){
    while(TRUE){
        product();//生产一个产品
        p(&empty);//申请一个空槽
        p(&mutex);//互斥访问临界区
        append();
        v(&mutex);//离开临界区
        v(&full);//非空槽+1
    }
}

void consumer(){
    while(TRUE){
        p(&full);//申请一个非空槽
        p(&mutex);//互斥访问临界区
        remove();//缓冲区拿走一个
        v(&mutex);//离开临界区
        v(&empty);//非空槽+1
        consume();
    }
}

理发师问题

理发店有1位理发师和1个椅子。
没有顾客的时候理发师在椅子上睡觉
顾客来的时候,如果理发师在睡觉,就叫醒理发师,剪完之后离开。

Semaphore_t customers = 0//等待的顾客数
            barbers = 0;//空闲的理发师数
//理发师进程
void barber(){
    while(TRUE){
        p(&customers);//没有顾客时候睡觉
        V(&barbers);//一个理发师休息
        cut_hair();//为一位顾客理发
    }
}

//顾客进程
void customer(){
    V(&customers);//来了一个顾客
    p(&barbers);//等候理发
    get_haircut();//开始理发
}

理发店有1位理发师、一个理发椅子,和3个等候理发椅子。
没有顾客的时候,理发师在椅子上睡觉
顾客来的时候,如果没有空椅子坐,则离开。
如果理发师忙,有空椅子坐,坐下等候
理发师在睡觉,就叫醒理发师,剪完之后离开。

#define CHAIRS 3
Semaphore_t customers = 0//等候理发的顾客数
            barber = 0//等候顾客的理发师数
            mutex = 1//互斥

int waiting = 0//等待理发的人数

void barber(){
    while(TRUE){
        p(&customers);
        p(&mutex);
        waiting--;
        V(&mutex);
        V(&barbers);
        cut_hair();
    }
}

void customer(){
    p(&mutex);
    if(waiting >= CHAIRS){//如果没有椅子就离开
    V(&mutex);
    return ;
    }
    waiting++;
    V(&customers);
    V(&mutex);
    p(&barbers);
    get_haircut();
}

哲学家进餐问题

#define TRUE 1
#define N 5//哲学家的个数
Semaphore_t fork[] = {1,1,1,1,1};
void philosopher(int i){
    while(TRUE){
        think();
        p(&fork[i]);
        p(&fork[i+1] % N);
        eat();
        V(&fork[i]);
        V(&fork[i+1] % N);
    }
}

这样写会导致死锁的问题,当5位同时拿起左边的叉子,就会相互等待
所以我们就需要设置一个互斥访问的信号量,能保证5个人不要同时拿起左边的叉子

#define TRUE 1
#define N 5//哲学家的个数
Semaphore_t fork[] = {1,1,1,1,1}, mutex=1;
void philosopher(int i){
    while(TRUE){
        think();
        p(&mutex);
        p(&fork[i]);
        p(&fork[i+1] % N);
        V(&mutex);
        eat();
        V(&fork[i]);
        V(&fork[i+1] % N);
    }
}

这里除了互斥叉子的5个信号量外,再引入一个信号量来互斥拿叉子的动作。
但是这样做的问题是同时只能有一个人能够吃饭。

#define TRUE 1
#define N 5//哲学家的个数
Semaphore_t fork[] = {1,1,1,1,1}, e=N-1;
void philosopher(int i){
    while(TRUE){
        think();
        p(&e);
        p(&fork[i]);
        p(&fork[i+1] % N);
        eat();
        V(&fork[i]);
        V(&fork[i+1] % N);
        V(&e);
    }
}

这里除了互斥叉子的5个信号量外,再引入1个信号量e=4,最多同时允许4个人同时吃饭,保证一个人能够拿到左右两个叉子

#define TRUE 1
#define N 5//哲学家的个数
Semaphore_t fork[] = {1,1,1,1,1};
void philosopher(int i){
    while(TRUE){
        think();
        if(i == N -1){
            p(&fork[0]);
            p(&fork[N-1]);
        }else{
            p(&fork[i]);
            p(&fork[i+1]);
        }       
        eat();
        V(&fork[i]);
        V(&fork[i+1] % N);
    }
}

这里提供的一种解决方案是不要让所有的人都同时拿起左边的叉子
也就是到最后一个人的时候,先拿右边的叉子,这样就避免了相互等待

#define TRUE 1
#define N 5//哲学家的个数
#define LEFT(i-1+N) % N //哲学家i的左邻居
#define RIGHT(i+1) % N //哲学家i的右邻居
#define THINKING 0 //哲学家正在思考
#define HUNGRY 1//哲学家想取叉子
#define EATING 2//哲学家正在吃饭
int state[] = {THINKING,THINKING,THINKING,THINKING,THINKING};//哲学家状态
Semaphore_t mutex = 1;//临界区互斥
            s[] = {0,0,0,0,0};//表示哲学家是否具备到得到叉子吃饭的条件

void philosopher(int i){
    while(TRUE){
        think();
        take_forks(i);//取左右2把叉子
        eat();
        put_forks(i);//放左右2把叉子
    }
}

void take_forks(int i){
    p(&mutex);//进入临界区
    state[i] = HUNGRY;
    test(i);//看是否能进餐
    V(&mutex);//离开临界区
    P(&s[i]);//取得叉子进餐
}

void put_forks(int i){
    p(&mutex);//进入临界区
    state[i] = THINKING;
    test(LEFT);//唤醒满足条件的左邻居进餐
    test(RIGHT);//唤醒满足条件的右邻居进餐
    V(&mutex);//离开临界区
}

void test(int i){
    if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING){
    state[i] = EATING;
    V(&s[i]);
    }
}

这里哲学家进餐的时机:饥饿并且左右邻居不在进餐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值