【进程管理】之进程同步与互斥

目录

进程同步

进程互斥

软件实现方法

双标志先检查法

双标志后检查法

Peterson算法

硬件实现方式

信号量机制

整型信号量

记录型信号量

管程


进程同步

进程有异步性。异步性是指,各并发执行的进程都在以不可预知的速度向前推进,并且互不干扰。

但是进程之间也需要对一些公共资源进行访问、占用,由于进程的异步性,导致各个进程的指令执行有着多种情况并且不可控制,那么共享资源可能分配不合理导致程序不能正常执行。

进程同步:也称为直接制约关系。指的是一些(俩个以上)的进程在某些位置上需要按规定的次序执行,以防止资源访问不合理。因为对进程的执行次序有规定,所以同步也是对进程的一种制约。进程之间的之间制约关系就是源于他们之间的相互合作。

进程互斥

进程”并发“需要有“共享”的支持。各个并发执行程序不可避免的共享一些公共资源(磁盘、内存、摄像头等)。

资源共享的俩种方式

互斥共享:系统中虽然给多个进程提供共享资源,但是有些资源在同一时间只能被一个线程访问。这些资源的这种访问方式就是互斥的。

同时共享:系统中的某些资源同一时间允许多个进程同时访问,这种方式为同时共享方式。

临界资源:通过互斥的方式才能访问的资源。

进程互斥:当一个进程访问临界资源时,其他进程如果也想访问,就需要等待直到当前访问的进程结束并且释放资源后,其他进程才能访问(只允许一个进程访问,其他进程仍需排队等待)。

互斥:面对临界资源时进程之间相互排斥,一个进程”霸占“了临界资源后,就不允许其他进程使用了,只能“惦记着”。

进程互斥:

对临界资源的互斥访问,可以在逻辑上分为四个部分:进入区、临界区、退出区、剩余区。

do {
    entry section;      //进入区 在此区内对临界资源进行锁定(上锁),阻止其他进程访问。
    critical section    //临界区 访问临界资源的执行代码
    exit section;       //退出区 在此区内释放临界资源,可供其它进程访问了。
    remainder section;  //剩余区
}while (true)

思考:如果一个进程暂时进不去访问临界资源,那么该进程是否要一直占着处理机?该进程有没有可能一直访问不了临界资源?

为了实现临界资源的互斥访问,同时保证系统的性能,应遵循以下原则

  • 空闲让进:如果临界资源没有被任何一个进程访问,那么就让一个请求访问临界资源的进程立即进入临界区。

  • 忙则等待:如果临界资源的锁没有被释放,那么请求访问的进程必须等待。

  • 有限等待:对于所有请求访问临界资源的进程,应该保证它们能够访问到临界资源。

  • 让权等待:当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。

    权:CPU执行权;

    忙等待:是指CPU执行的进程在等待访问临界资源,无法执行其他工作。这时CPU在为该进程而服务,在一直等待,无法为其他进程服务。俗称“干等着”。

软件实现方法

单标志法

算法思想:一个进程在访问临界资源后,将访问权限交给另一个进程。也就是说进程的访问权限只能是另一个进程给它的。

代码示例:

int  turn = 0;  //表示允许访问的进程号
//p0进程
while(turn != 0);   //进入区
critical section;   //临界区
turn = 1;           //退出区
remainder section;  //剩余区
​
//p1进程
while(turn != 1);   //进入区
critical section;   //访问临界区
turn = 0;           //退出区
remainder section;  //剩余区

turn为访问权限,turn的值代表拥有权限的进程号。

因此,该算法可以实现“同一时间只允许一个进程访问临界区”。

如果现在turn = 0,代表此时临界资源的访问权限是属于0号进程的,但此时处理机将执行权分配1号进程,那么1号进程会一直轮询尝试进入临界区,由于他的权限只能是由具有权限的0号进程赋予,而0号进程没有执行权,那么1号只能一直轮询无法进入临界。只有当处理机将执行权重新分配给0号进程,而0号进程访问完临界区后将访问权限赋予1号进程,1号进程在执行的时候才能访问。

因此,该算法无法实现“空闲让进”。

双标志先检查法

算法思想:设置一个boolean数组,数组中的元素该号(用下标表示)代表进程是否有意愿访问临界资源。每个进程在访问临界资源时先检查(名字的由来)是否有其他进程想要访问临界资源,如果没有,将告诉其他进程自己想要访问临界区(flag[小标/进程号] = true,也就是上锁),然后进入临界区。

代码示例:

bool flag[2];  
flag[0] = false, flag[1] = false;
//p0进程
while(flag[1]);     1
flag[0] = true;     2
critical section;   3
flag[0] = false;    4
remainder section;
​
//p1进程
while(flag[0]);     5
flag[1] = true;     6
critical section;   7
flag[1] = false;    8
remainder section;

这种算法相比较于单标志法,在进入区时,不仅要检查是否有其他进程在访问临界区,同时也表达自己的意愿,解决了单标志法的"空闲让进"的问题。

如果P0先执行1号指令检查是否有进程在访问临界区,因为没有进程有意愿访问,所以P0进程就可以表达自己的意愿执行2号指令,但是在执行之前处理机调度至P1进程执行,P1进程就可以执行5号指令,由于P0进程当时还没有执行2号指令,因此此时也没有进程有意愿访问临界区,那么P1进程就可以继续向下执行,当P1访问进入临界资源时,处理机又调度至P0进程执行,那么P0进程也向下执行,那么俩个进程将同时进入临界区。(按照152637...)

因此,这种算法违反了“忙则等待”的原则。

上面这种情况能出现的原因就是1、2条指令不是原子性的(没有“‘一气呵成”),也就导致违反了“忙则等待”。

双标志后检查法

算法思想:是双标志先检查法的改版。双标志先检查法由于检查和上锁没有一气呵成,并且是先检查后上锁,导致违反了“忙则等待”的问题。而双标志后检查法是通过先上锁后检查来保证“忙则等待”。

代码示例:

bool flag[2];  
flag[0] = false, flag[1] = false;
//p0进程
flag[0] = true;     1
while(flag[1]);     2
critical section;   3
flag[0] = false;    4
remainder section;
​
//p1进程
flag[1] = true;     5
while(flag[0]);     6
critical section;   7
flag[1] = false;    8
remainder section;

处理机还是按照刚才的调度按照(152637...)顺序执行,在1号指令执行后调度,另一个进程执行5号指令后再切回来,那么此时这俩个进程谁也别想进入临界区了.....

双标志后检查法虽然保证了"忙则等待"原则,但是有违反了"空闲让进"和"有限等待"原则。

Peterson算法

算法思想:将单标志法和双标志法结合起来。在进程争抢资源互不让步的时候,要谦让对方,让对方先访问。

代码示例:

bool flag[2];  
flag[0] = false, flag[1] = false;
int turn = 0;
//p0进程
flag[0] = true;                  1           //表达自己的意愿
turn = 1;                        2           //谦让对方
while(flag[1] && turn == 1);     3   //如果1号进程想要使用临界区,而且我还让给它了,就等他一会吧
critical section;                4
flag[0] = false;                 5
remainder section;
​
//p1进程
flag[1] = true;                  6
turn = 0;                        7
while(flag[0] && turn ==0 );     8    //如果0号进程想要使用临界区,而且我还让给它了,就等他一会吧
critical section;                9
flag[1] = false;                 10
remainder section;

在0、1号进程都想要访问临界区时,都会互相谦让,哪个进程最后表达了谦让,哪个进程就会先丧失访问权,进行等待。

总结:Peterson算法解决了进程互斥问题,也遵循了“空闲让进”、“忙则等待”、“有限等待”的原则,但依旧没有保证“让权等待”。

硬件实现方式

中断屏蔽方式

利用开/关中断指令实现 与原语中的实现思想相同,即在某进程开始访问临界区到访问结束为止都不允许中断 也就是不能发生进程切换,因此不可能发生两个同时访问临界区的情况

进程切换是通过中断实现的,屏蔽中断也就意味着进程不会切换了。

优点:简单高效。

缺点

  • 不适合多处理机。

    中断屏蔽只对执行关中断指令的处理机生效,多处理机系统中,使用中断屏蔽来阻止进程调度的目的就达不到了。

  • 不适合用户进程

    开/关中断指令是内核指令,只有在内核态才有权限执行,用户进程没有权限执行这个指令。

TestAndSet指令

简称TS指令或者是TSL指令 使用硬件实现的 执行的过程不允许被中断 只能是一气呵成 下面用C语言描述一下(只是描述一下逻辑,是有硬件实现这套逻辑的)

//bool类型共享变量lock表示当前临界区是否被加锁
//true表示被加锁 false表示未加锁
bool TestAndSet(bool *lock){
    bool old;
    old=*lock;//old用来存放lock原来的值
    *lock=true;//无论之前是否被加锁 都把lock设置为加锁状态
    return old;//返回原来的值
}
//下面是TLS指令实现互斥算法的逻辑
while(TestAndSet(&lock));//上锁 并 检查
临界区代码段
lock=false;//临界区执行完毕 解锁
剩余区代码段

TestAndSet(bool *lock)这个方法既检查了所得状态,同时也上了锁。在这段逻辑代码中,上锁并检查时调用这个方法代表着上锁和检查是一气呵成的。在软件实现方式中,双标志法的那俩种情况都是由于上锁和检查是分开执行的。

优点:简单高效,无需像软件实现方法那样严格检查是否会有逻辑漏洞,适用于多处理机环境

缺点:不满足“让权等待”。

暂时无法进入临界区的进程会占用CPU并且执行TS指令

Swap指令

也叫Exchange指令 或者简称XCHG指令 从逻辑上看Swap指令和TSL指令没有太大的区别

//Swap指令的作用就是交换两个变量的值
bool Swap(bool *a ,bool *b){
    bool temp;
    temp=*a;
    *a=*b;
    *b=temp;
}
​
//下面是TLS指令实现互斥算法的逻辑
bool old=true;
while(old==true)
Swap(old,&lock);
临界区代码段
lock=false;//临界区执行完毕 解锁
剩余区代码段

优点与缺点与TS指令类似。

互斥锁

特性

  • 需要忙等,进程时间片用完才下处理机,违反“让权等待”。

  • 优点:等待期间不需要进行进程的上下文切换,在多处理机上,若上锁时间段,则等待的代价很低。

  • 常用于多处理机,忙等的进程只占用一个处理机,其他处理机正常运行处理工作,那么锁就会很快被释放。

  • 不适用于单处理机,因为忙等的进程占用了唯一的一个处理机,那么占用临界区进程的作业无法处理,那么锁在短时间内不会释放,代价过大。

信号量机制

为什么要引入信号量?

1.之前的双标志法中,检查和上锁两个操作没有一气呵成,导致进程互斥、异步的过程中按照特定序列执行的话会出问题。

2.之前所有的方法都不能解决不遵循“让权等待”的问题。

信号量:其实就是一个变量,用这个变量来描述系统剩余资源的数量。一个信号量代表一种资源的剩余数量。

这个变量可以是一个整数,也就是下文的整型信号量,

也可以是一个结构体,也就是下文的记录型信号量。

用户进程可以使用系统提供的一对原语来对信号量进行操作,从而实现进程的互斥与同步。

一对原语:wait(P)和signal(S)。

wait、signal也简称为P、V操作。P、V来自于荷兰语proberen和verhogen。

信号量机制是1965年,荷兰学者Dijikstra提出的。

整型信号量

用一个整数形的变量来表示系统中某种资源的数量。

//eg:比如系统中有一台打印机.
int S = 1; //用它作为信号量,来表示打印机的数量。
void wait(S){ //P操作
    while(S <= 0); //s<=0代表系统中可用的打印机数量没有了,那么while循环一直等待
    s -= 1;        //到这里说明有可用的打印机资源了,但是这个进程要使用一个,所以S -= 1
}
​
void signal(S){//V操作
    s += 1;
}
​
//进程
wait(S);       //进入区,检查并上锁
使用打印机       //临界区,使用资源
signal(S);     //退出区,释放资源
​

优点:检查和上锁的俩个操作都在wait原语中,是不可中断的,那么就不会有双标志法中的特殊序导致的问题。

缺点:暂时不能进去临界区的进程依然在while里面自旋,不能遵循“让权等待”。

记录型信号量

使用数据结构来记录的信号量。

typedef struct {
    int value;            //剩余资源数
    struct process *L;    //等待队列,在V操作中可以通过等待队列唤醒进程
}semaphore;
记录型信号量中的P、V操作

void wait(S){
    S -= 0;
    if(S.value < 0){
        block(S.L);   //是一个原语,是进程从运行态变为阻塞态
    }
}
​
void signal(S){
    S += 1;
    if(S.value <= 0){
        wakeup(S.L);  //是一个原语,用来唤醒进程,是进程从阻塞态变成就绪态。
    }
}

记录型信号量机制的wait操作检查是否有可用的资源,如果没有则主动让自己阻塞(通过block原语实现),也就是说主动放弃了处理机的执行权。

signal中检查有可用资源的话,就唤醒进程。

注意:P、V操作必须成对的出现,缺一不可。

如果没有P操作,那么临界资源的互斥访问就没法保证了;

如果没有V操作,那么会导致资源永不释放,阻塞的进程将无法被唤醒。

总结:记录型信号量不仅能够保证进程互斥的访问临界资源,也能够实时记录系统资源的情况,然后动态的阻塞进程唤醒进程,从而遵循了前面方案中都没有解决的不遵循“让权等待”问题。

管程

为什么引入管程?

信号量机制虽然能够实现进程互斥与同步,也遵循"让权等待"的原则。但是对于不同资源,在申请这些资源时的顺序也要合理严谨,不然很可能会导致死锁。因此加大了程序程序员的编码难度。

管程的组成和基本特征

管程是一中特殊的软件模块,有这些部分组成:

  1. 在管程内部的共享数据结构。

  2. 对管程内部共享数据结构进行操作的一组过程。

  3. 对管程内部共享数据设置初始值的语句。

  4. 管程要有一个名字。

学习tip:管程与Java中的类概念很像。

第1个模块相当于类中的私有属性。

第2个模块相当于类中的get和set方法。

第3个模块相当于类中的构造方法或者静态代码块。

第4个模块相当于类名。

管程的基本特征:

  1. 管程内部的数据结构能能被管程内部的过程所访问。

  2. 每次仅允许一个进程在管程内执行某个内部过程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值