- 进程合作:多进程共同完成一个任务。
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) 函数用于生产资源,来决定是否从等待队列中唤醒别的进程的。