同步机制(下)

管程:

为什么引入:

信号量机制的不足,而且用PV操作编写程序困难

概念:

一个特殊的模块,由关于共享资源的数据结构及在其上操作的一组过程组成

上图即为管程,进程通过调用管程来实现对共享资源的操作,即进程只能通过调用管程中的过程来间接地访问管程中的数据结构

要求:

作为管程主要是为了同步机制:

1.做到互斥:

为了保证管程中共享资源的数据结构完整性要做要进程间互斥。

2.同步:

管程中设置条件变量及等待/唤醒操作以解决同步问题,可以让一个进程或线程在条件变量队列上等待(此时,应先释放管程的使用权,也就是其他进程可以进入管程),也可以在另个进程执行过程中通过发送信号将等待在条件变量上的进程或线程唤醒。

但是上述的同步也有问题:

首先它说明了可以有两个进程进入管程,其次唤醒了等待进程后,被唤醒进程会如何,唤醒进程会如何?

三种处理方法:
1.唤醒进程P等待,被唤醒Q执行(hoare)
2. 被唤醒进程Q等待,唤醒进程P继续执行(MESA)
3. 规定唤醒操作为管程中最后一个可执行的操作(Hansen,并发pascal)

 

HOARE管程:

管程由资源,许多过程,条件变量等待队列,紧急等待队列,入口等待队列组成。

执行过程:

想进入管程的进程首先排在入口等待队列,之后某个进程P进入管程,然后将管程使用权关闭,阻止其他进程再进入管程,如果进程P在CPU上执行一段时间后,由于执行条件等问题从而进入对应的条件变量队列等待同时将管程使用权开启,假设此时Q进程进入管程,执行一段时间后将P唤醒,此时Q进程将会进入紧急等待队列,然后P进程上CPU执行,等它执行完后,会首先判断紧急等待队列(优先级高)中是否有进程,没有再从入口等待队列再调入进程 

 

条件变量:在管程内部说明和使用的一种特殊类型的变量 

对于条件变量可以执行wait()/signal()函数:

wait(c):
如果紧急等待队列非空,则唤醒队列中第一个等待者;否则释放管程的互斥权,让外部入口等待队列第一个进程进入管程,然后执行此wait操作的进程进入c对应的条件变量c链末尾
signal(c):
如果c链为空,则相当于空操作(没有因条件变量c而等待的进程),执行此signal操作的进程继续执行;否则唤醒C链中第一个等待者执行此signal操作的进程进入紧急等待队列的末尾

 

管程应用:

管程的实现:

1.直接构造 → 效率高

2.间接构造→ 用某种已经实现的同步机制去构造,例如用信号量及P、V操作构造管程

 

使用管程解决生产者消费者问题:

 

count初始值为0表明数据数量,N为缓冲区大小

第一红线:count等于缓冲区大小,表明缓冲区中更没有空位置放新数据,此时把insert该进程通过wait函数插到full条件变量队列中。

第二红线:count等于1,表明之前count = 0,这可能导致之前的某个remove进程进来时因为没数据而等待在empty队列中,所以通过signal唤醒empty队列中首进程

第三红线:count等于0,表明没有数据,此时通过wait将remove进程放到因empty条件变量的队列中

第四红线:count等于N-1,表明之前count = N,这可能导致之前的某个insert进程进来时因为缓冲区已满而等待在full队列中,所以通过signal唤醒full队列中首进程

 

MESA管程:

对比HOARE发现,假如原先P进程运行后,因为条件变量而等待(一次进程切换),Q将其唤醒,之后上CPUQ下CPU(一次进程切换),所以总共花了两次进程唤醒

改进:

将signal换成notify(),notify作用为:

Q唤醒P后,Q继续执行。

然后:

之后位于条件队列头(之前因为在条件首采唤醒它)的P进程在将来合适的时候且当处理器可用时恢复执行

并且由于不能保证在P进程执行之前没有其他进程进入管程,可能这个进来的进程会改变条件变量,从而影响P进程,因而这个P进程必须重新检查条件,才能上CPU(用while循环取代if语句)

 

MESA管程优缺点:

导致对条件变量至少多一次额外的检测但不再有额外的进程切换),并且对等待进程在notify之后何时运行没有任何限制(只要运行前做检查即可)

 

MESA管程解决生产者消费者问题:

假设append进程执行,若起始count为N,即缓冲区中没有空位置,那么调用cwait将append进程置为等待态放到full队列中,并且其他append队列也会阻塞,之后等take进程执行将count减1,等待的append进程才会进行。若起始count不为N,那么将输入的数据放到nextin所指向的缓冲区位置中,count++,并且唤醒等待在empty队列中的take进程,但是接下来还是append执行,直到进程切换才轮到唤醒的take进程。对take进程分析同理。

 

改进notify():

给每个进程关联一个监视计时器,不论是否被通知,一个等待时间超时的进程将被设为就绪态,并且当该进程被调度执行时,会再次检查相关条件,如果条件满足则继续执行

这样做可以防止当某些进程在产生相关条件的信号(唤醒某需要该条件的进程)之前失败时,等待该条件的进程就会被无限制地推迟执行而处于饥饿状态的现象产生

 

 

引入broadcast:使所有在该条件上等待的进程都被释放并进入就绪队列

适用范围:

1.当一个进程不知道有多少进程将被激活时,这种方式是非常方便的

  例子:生产者/消费者问题中,假设insert和remove函数都适用于可变长度的字符块,此时,如果一个生产者往缓冲区中添加了一批字符,它不需要知道每个正在等待的消费者准备消耗多少字符,而仅仅执行一个broadcast,所有正在等待的进程都得到通知并再次尝试运行

2.当一个进程难以准确判定将激活哪个进程时,也可使用广播

 

 

HOARE与MESA管程比较:

1.Mesa管程优于Hoare管程之处在于Mesa管程错误比较少(执行时会重新检查条件变量)
2.在Mesa管程中,由于每个过程在收到信号后都重新检查管程变量,如果期望的条件没有满足,它会重新继续等待,并且由于使用了while结构,一个进程不正确的broadcast广播或发信号notify,不会导致收到信号的程序出错

 

实现管程结构必须保证下面几点:
(1)只能通过管程的某个过程才能访问资源;
(2)管程是互斥的,某个时刻只能有一个进程或线程调用(而不是进入,可以多个进入)管程中的过程

 

PTHREAD中同步机制:

相关的函数:

 

 

 

解决生产者消费者问题:

produce进程先执行,先锁上mutex,再往缓冲区中输入数据(while条件导致只有buffer为0时,produce进程才会往缓冲区中输入数据),然后唤醒condc队列中consume进程来去数据,同理consume进程。

 

pthread_cond_wait的三个主要动作:

1、解锁(将某个进程放入条件变量等待队列中,所以将管程使用权打开,让外部入口等待队列中进程进入)
2、等待
当收到一个解除等待的信号(pthread_cond_signal或者pthread_cond_broad_cast)之后,pthread_cond_wait马上需要做的动作是:
3、上锁(感觉这个上锁,之前要是某个进程进入管程也会上锁,不用这时候再上锁,除非在HOARE管程中,进程Q将P唤醒,然后它马上等待,同时又将管程打开锁,此时若唤醒的P要执行,那么需要它去关闭锁)

 

进程间通信机制:

为什么需要该机制:

1.信号量及管程的不足

2.管程不适用多处理器情况

通信主要包含send和receive原语。

 

基本通信方式:

1.消息传递

2.共享内存

3.管道

4.套接字

5.远程过程调用

 

消息传递:

执行过程:首先发送进程执行send原语,根据destination找接收进程,如果未找到,出错返回,然后CPU从用户态变成内核态,申请空缓冲区,把消息从message处复制到空缓冲区,把消息缓冲区挂到接收进程的消息队列,然后接收方执行receive原语,根据source在消息队列中找到所发信息的进程,没找到则出错返回,之后将缓冲区message拷贝到自己进程地址空间message。

 

PV实现的send原语:

 

 用消息传递解决生产者消费者问题:

 

 共享内存:

要建立该通信机制要解决:

1.在内存中分配一个共享空间,并将物理地址映射到两个进程地址空间中某块区域

2.要对共享内存进行保护,例如写和读不能同时进行。

 

管道通信:

设计思想:

利用一个缓冲传输介质内存或文件连接两个相互通信的进程

管道注意事项:

字符流方式写入读出

先进先出顺序

管道通信机制必须提供互斥、同步、判断对方进程是否存在的协调能力

 

 

linux中进程通信:

原子操作:

1.不可分割,在执行完之前不会被其他任务或事件中断
2.常用于实现资源的引用计数
3.用atomic_t定义

屏障:

一种同步机制(又称栅栏、关卡),用于协调线程

应用场景

一组线程协同完成一项任务,需要所有线程都到达一个汇合点后再一起向前推进

 

 

作者水平有限,文章肯定有错还请各位指点!!!感谢!!!

 

posted on 2017-07-26 23:30  chaunceyctx  阅读(1192)  评论(0编辑  收藏  举报

导航