0%

L16-进程同步与信号量

  • 进程合作:多进程共同完成一个任务。

L16 进程同步与信号量

进程同步与合作

  • 进程合作:多进程共同完成一个任务。

  • 进程同步与合作仍用之前经典的生产者-消费者例子讲述:

    为了让生产者进程和消费者进程能在正确的顺序上运行,需要让这两个进程之间能够“交流”,我们可以通过“信号”让这两个进程实现交流,比如说当生产者进程生产的东西填满缓冲区后,该进程就应该陷入空循环,等待消费者进程消费产品,只有当消费者进程消费掉一个产品,让 counter-- 后,生产者进程才能够继续生产,它们之间交流的信号就是 “counter” 。

    进程同步的核心就是”等待“,一个进程要适时地等待另外一个进程做完一些事后,再继续运行,才能运行在正确的顺序上,而一个进程什么时候才能知道另一个进程已经做完了一些事,可以继续运行了,就需要信号来告诉它。

    进程同步 = 等待 + 唤醒。

★★★信号量

  • 但只有信号还不足以解决问题,继续看生产者-消费者例子,假设此时有生产者P1、生产者P2和消费者C三个进程,当 counter==BUFFER_SIZE 时,生产者进程会陷入睡眠,假设此时缓冲区满,则P1、P2都陷入睡眠,等待被唤醒;然后消费者进程开始运行,当消费者进程消费掉一个产品后, counter==BUFFER_SIZE-1 ,此时消费者进程C会唤醒一个生产者进程,假设是P1,P1被唤醒,然后消费者进程C继续运行, counter==BUFFER_SIZE-2 ,消费者进程C不会再唤醒生产者进程了,就意味着P2没有机会被唤醒,出现了问题。

    原因在于只有信号 counter 是不够的,它所蕴含的信息太少,不能反映到底是有几个生产者进程陷入了睡眠,因此需要采用信号量机制:不再是根据 counter 来发信号了,而要根据信号量来发信号,通过信号量的值来决定进程什么时候等待,什么时候要被唤醒。

    用 sem 来表示信号量(semaphore):

    当缓冲区满时,P1执行,发现缓冲区满,于是P1睡眠,sem = -1,表示这时有一个生产者进程陷入睡眠;

    然后P2执行,也发现缓冲区满,于是P2睡眠,sem = -2,表示这时有两个生产者进程陷入睡眠;

    然后C执行,消耗一个产品,并发现 sem = -2,知道有两个生产者进程陷入了睡眠,于是唤醒其中一个(假设是P1),sem = -1,表示这时还剩一个生产者进程(P2)陷入睡眠,而被唤醒的P1又生产了一个产品,此时缓冲区还是满的;

    然后C继续执行,消耗一个产品,发现 sem = -1,知道有一个生产者进程陷入了睡眠,于是唤醒它,sem = 0,表示这时没有生产者进程陷入睡眠,而被唤醒的P2又生产了一个产品,此时缓冲区还是满的;

    当C再次执行的时候,消耗一个产品,sem = 1,表示此时缓冲区有一个空闲的位置可以让生产者进程生产产品

    然后P3执行,生产一个产品,sem = 0,表示缓冲区满了。

  • ★★★接下来就用信号量来解决前面提出的生产者-消费者问题。

    这里用到了三个信号量:

    • full 表示缓冲区中资源位置的个数,初值为0,当 full = 0 时就代表缓冲区中没有资源了;
    • empty 表示缓冲区中空闲位置的个数,初值为 BUFFER_SIZE,当 empty = 0 时就代表缓冲区满了;
    • mutex 为互斥信号量(mutual exclusion),初值为 1,其功能就类似于“L9-多进程的合作”中提到的“锁”,当 mutex = 1 时,表示该进程可以继续向下运行,当 mutex <= 0 时,表示有其他进程正在运行,该进程就需要排队进行等待。

    P(semaphore s) 表示消费资源;V(semaphore s) 表示生产资源,这两个函数的代码见上图。

    • 当生产者进程开始执行时,首先生产者进程通过 P(empty) 先将 empty 减 1,表示消耗一个空闲位置资源,然后检查缓冲区中是否有空闲位置资源(即 empty 是否 <0),如果没有空闲位置资源(即 empty < 0),则该进程要进入 empty等待队列 等待被唤醒,如果有空闲位置资源(即 empty >=0),
    • 则该进程通过 P(mutex) 先将 mutex 减1,然后检查是否有进程正在执行(即 mutex 是否 <0),如果有进程正在执行(即 mutex < 0),则该进程要进入 mutex等待队列 等待被唤醒,反之该进程继续向下运行,读入 in,将 item 写入到 in 的位置上,
    • 等到该进程执行完写操作后,通过 V(mutex) 先将 mutex 加1,然后检查 mutex 是否 <= 0(即是否有进程在 mutex 等待队列),如果是,则唤醒 mutex等待队列 中的第一个进程,如果 mutex > 0(即 mutex = 1,表示别的进程此时能够执行而不受阻了),则继续向下执行,
    • 通过 V(full) 先将 full 加1,然后检查 full 是否 <=0(即是否有*消费者进程*在 full等待队列 ),如果是,则唤醒 full等待队列 中的第一个消费者进程。如果 full > 0,则表示只是让缓冲区中的资源位置的个数加1,并不涉及唤醒 full等待队列 的操作(此时 full等待队列 应该为空)。

    消费者进程同理。

    注意:进程从哪里睡眠(即进入等待队列)就要从哪里被唤醒继续向下运行。

    ​ 由于信号量 semaphore 包含 PCB 和多个进程共用的 value,所以信号量应该保存在内核里

  • ★★★信号量最重要的两张图就是 L16-6 和 L16-7 两张图,其中

    • P(semaphore s) 函数用于消费资源,来决定该进程是否进入等待队列的; (不管三七二十一,先消费一个资源再说)
    • V(semaphore s) 函数用于生产资源,来决定是否从等待队列中唤醒别的进程的。
---------------The End---------------