• 【操作系统】进程间通信


    竞争条件
    两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞争条件。凡涉及到资源的共享时就easy发生这样的事情。

    解决的办法是设立临界区,让进程相互排斥地訪问共享资源。

    一个好的避免竞争条件的方案,必须满足4个条件:

    1. 不论什么两个进程不能同一时候处于临界区。

    2. 不应对CPU的速度和数量做不论什么如果。

    3. 临界区外运行的进程不得堵塞其他进程。
    4. 不得让进程无限期等待进入临界区。

    忙等待相互排斥
    1.屏蔽中断
    进程在进入临界区时屏蔽中断(包括时钟中断),离开临界区时打开中断。这使得CPU无法切换到其他进程。

    这样的方案的缺点在于,将中断屏蔽的进程可能不再将中断打开。导致CPU永远无法切换进程;并且屏蔽的仅仅是该进程相应的CPU的中断。其他没有被屏蔽中断的CPU仍然能够訪问共享资源。在多核系统中,这样的方法并不适用。


    2、锁变量
    进程进入临界区之前必须先持有锁,然后将锁占有,其他进程得不到所而在临界区外等待。但这样的方案的缺陷在于,进程在推断锁可用到占有锁之间可能会被调度,还有一个进程相同发现锁可用并进入临界区。这会导致两个进程同一时候进入临界区。

    3、严格轮换法
    进程等待某个变量被置位后才干进入临界区,例如以下图所看到的:

    进程a在turn变为0之前循环等待。进程b在turn变为1之前循环等待。

    这属于忙等待,非常显然浪费了CPU时间。

    用于忙等待的锁称为自旋锁

    这样的方案的问题在于,两个进程必须依照严格的顺序交替进入临界区,这会减少速度较快的进程的运行效率。也就是违反了上述条件3.


    4、Peterson算法
    该算法非常easy且有效。仅由两个C函数构成:

    关键的一点在于enter_region函数中的while循环,当两个进程同一时候进入enter_region函数时,能够确保先进入该函数的进程进入临界区,而后进入的在while循环出等待。

    5、TSL/XCHG指令
    TSL指令的形式例如以下:
    TSL RX, LOCK    # 将LOCK读入寄存器RX并将1写入LOCK所在内存,读和写是一个不可切割的原子操作
    运行TSL的CPU将锁住总线,这使得其他不论什么CPU都无法訪问共享内存,相当于屏蔽中断的改进版。使用TSL指令的用法例如以下所看到的:

    还有一条指令XCHG原子性地交换两个位置的内容,所以它可作为TSL指令的替代品控制进程进入临界区,原理实际上和上图是一样的,例如以下图所看到的。全部的Intel x86 CPU在底层同步中使用了XCHG指令


    休眠与唤醒
    上述方案有一个共同的缺点就是进程在无法进入临界区时。处于忙等待状态。这浪费了CPU时间。要使进程在无法进入临界区时堵塞,而不是等待,能够使用进程间通信原语。比如:sleep、wakeup等。下面是另外一些进程间相互排斥、同步的方案。


    1、信号量
    由大神Dijkstra提出,它包括down(P,表示尝试)和up(V。表示添加)两种操作,所以又称PV操作:
    • down:检查信号量的值是否大于0,大于0则减1并继续。等于0就休眠进程。整个操作不可切割。
    • up:对信号量值增1,唤醒因为down操作而休眠的进程,使其继续运行未完毕的down操作。整个操作不可切割。
    信号量可用于进程间的相互排斥和同步
    • 相互排斥:在同一时刻仅仅有一个进程能够进行操作。比如相互排斥地进入临界区。
    • 同步:进程间的运行须要依照某种先后顺序。比如生产者发现缓冲区满时要停止;消费者发现缓冲区空时要停止。
    使用信号量解决生产者-消费者的样例:

    当中,empty和full是用来实现同步的信号量。mutex是用来实现相互排斥地信号量。

    2、相互排斥量
    无计数能力。是信号量的一个简化版本号。

    相互排斥量包括两个状态:解锁(0)和加锁(1)。线程加锁和解锁函数mutex_lock、mutex_unlock的实现例如以下:


    这里的mutex_lock和上面的enter_region的差别在于:调用mutex_lock的线程无法进入临界区时会释放CPU(thread_yield函数)运行另外的线程;而调用enter_region会不断循环測试(忙等待),直到临界区可用。

    3、管程
    使用信号量和相互排斥量存在一个问题:死锁。

    比如上面的信号量部分代码中,如果将down(&empty)和down(&mutex)顺序对调,那么就有可能发生死锁。原因在于当生产者先锁住mutex,然后empty为0休眠后,消费者因为得不到mutex锁而休眠。这样两个进程将永远休眠下去。使用管程可解决这一问题。管程由过程、变量、数据结构等组成的一个模块,进程间必须相互排斥地訪问这个模块中的过程,例如以下图所看到的。管程的重要特性是,在任一时刻管程中仅仅能有一个活跃进程


    上图有一个名为example的管程,包括一个整型变量i、一个条件变量c和两个过程。管程为进程的相互排斥訪问提供了环境。接下来要解决的是同步问题。解决方法是使用条件变量:当管程中的过程发现自己无法继续运行时(比如生产者发现缓冲区满),会在某个条件变量身上运行wait操作堵塞自身并将其他进程调入管道。还有一个进程(如消费者)对缓冲区进行消费后。能够调用signal向条件变量发送信号以唤醒因调用wait而堵塞的进程(如生产者)。然后自身退出管程,被唤醒的进程进入管程。

    4、消息传递
    通过两条原语send和receive在进程间进行通信,send为发送消息而receive为接收消息(有可能发生堵塞)。也就是让消息称为共享资源的载体。使用消息传递机制解决生产者-消费者问题的代码例如以下:

    5、屏障
    一种应用于进程组的同步机制。它规定,全部进程都完毕了第n阶段,才干进入第n+1阶段。

    也就是说。当有进程完了第n阶段而还有一些进程没有完毕第n阶段时,完毕的那些进程是须要堵塞等待未完毕进程的,例如以下图所看到的。

    这能够在每一个阶段的末尾加入屏障来实现这一功能。



    參考:
    《现代操作系统》 P66-P82.
  • 相关阅读:
    金斗云提醒用法说明
    金斗云提醒软件的原理
    缓存雪崩问题,缓存击穿问题,缓存一致性问题(内存+数据库)
    Spring的ApplicationEvent实现
    区块链技术--区块链的生成和链接
    区块链技术--比特币交易的锁定和解锁
    区块链技术--密码学
    区块链技术--比特币
    jedis中scan的实现
    KafkaManager对offset的两种管理方式
  • 原文地址:https://www.cnblogs.com/mqxnongmin/p/10823615.html
Copyright © 2020-2023  润新知