操作系统——两个同步互斥问题的详细推导
1.前置知识
信号量 : 信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。
进程的互斥:是指当有若干个进程都要使用某一共享资源时,任何时刻最多只允许一个进程去使用该资源,其他要使用它的进程必须等待,直到该资源的占用着释放了该资源。
最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
进程的同步:是指在并发进程之间存在这一种制约关系,一个进程依赖另一个进程的消息,当一个进程没有得到另一个进程的消息时应等待,直到消息到达才被唤醒。
最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。
pv操作又称wait,signal原语。
主要是操作进程中对进程控制的信息量的加减控制。
wait:wait(num),num是目标参数,wait的作用是使其(信息量)减一。
如果信息量>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
signal:signal(num),num是目标参数,signal的作用是使其(信息量)加一。
如果信息量>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
可以将wait()理解为红灯,暂时无法通行,满足一定条件才能通行。
signal()理解为绿灯,让原本有红灯的位置可以通行。
2.司机与售票员问题
2.1问题背景
司机开车,售票员售票。
当售票员将门关上的时候司机才可以开车,
当司机将车到站停下的时候,售票员才可以打开车门。
首先写出代码块。
司机进程:
while(1){
//S1:观察路况
//S2:启动车辆
//S3:正常驾驶
//S4:到站停车
}
售票员进程:
while(1){
//S5:关车门
//S6:售票
//S7:开车门
}
2.2分析关系
分析这两个进程的关系,显然,两个进程中的代码块存在先后关系,所以是同步问题。
2.3确定信号量
启动车辆前要确保门是关着的(S7),开门前要确保车是停止的(S4),因此添加两个信号量close和stop,用来表示已经关门和已经停止。close初始化为0,表示车门未关闭,stop初始化为0,表示车未停稳
2.4添加代码
先从司机进程看起。
S1:观察路况,司机什么时候都可以观察路况,不受条件限制,因此前后都不需要添加代码。
S2:启动车辆,司机在开车前显然要确保车辆的门是关着的,所以要在S2前面添加wait(close),只有当close为1时,也就是车关门时才会启动车辆,否则等待。
S3:正常行驶前后也不需要添加。
S4:到站停车后,要释放stop,在S7添加signal(stop),表示车已经停止,可以开门。
司机进程:
while(1){
//S1:观察路况
wait(close);
//S2:启动车辆
//S3:正常驾驶
//S4:到站停车
signal(stop);
}
售票员进程
S5:关门前没有限制,不需要添加代码,关门后要添加signal(close),表示车门已关闭,这样车才能启动。
S6:前后没有限制。
S7:开车么前,要确保车是停止的。因此要在前面添加wait(stop),表示车已经停止才能继续,否则等待。
售票员进程:
while(1){
//S5:关车门
signal(close);
//S6:售票
wait(stop);
//S7:开车门
}
3.生产者消费者问题
3.1问题背景
缓冲区大小为N,假设有多个生产者和消费者,且只能有一个同时访问临界区
void producer()
{
while(1)
{
//生产
}
}
void consumer()
{
while(1)
{
//消费
}
}
3.2分析关系
生产者和消费者要共享缓冲区,不能同时使用缓冲区;因为有多个生产者和消费者,生产者和生产者,消费者和消费者也不能同时使用缓冲区,因此是互斥关系
生产者与消费者,也有同步关系,消费者只有在缓冲区里有数据时,也就是生产者生产后,才能消费
而消费者在缓冲区满后,无法继续生成,只有在消费者消费后,才能继续生产。
3.3确定信号量
添加信号量mutex ,初始化为1。用于互斥访问临界区
full, 初始化为 0,表示缓冲区内数据数量,用来阻塞消费者,即只有有数据是才能消费。
empty ,初始化为 N,表示缓冲区的空位置的数量,用来阻塞生产者,即只有有空位置时,才能生成。
3.4添加代码
先分析同生产者在生产前要确保有空位置,即empty>0,否则等待,如果大于0,则会执行后面的语句,同时empty会-1(因为生产后空位减少了)。所以要在生产前添加wait(empty)。
生产后要让full+1,表示缓冲区的数据增加1,因此要添加signal(full)。
消费者在消费前要确保缓冲区内有数据,即full>0,否则等待,如果大于0,则会执行后面的语句,同时full会+1(因为消费后数据减少了)。所以要在消费前增加wait(full)。
消费后要让empty+1,表示缓冲区的空位增加1,因此要添加signal(empty)。
void producer()
{
while(1)
{
wait(empty);
//生产
signal(full);
}
}
void consumer()
{
while(1)
{
wait(full);
//消费
signal(empty);
}
}
再分析互斥
因为生产和消费不能同时进行,同时不同的生产者和不同的消费者也不能同时进行,因此可以使用共同的信号量mutex,在生产和消费前增加wait(mutex),只有mutex等于1时才会执行后面的操作,否则等待,在一个进程执行时,会将mutex-1,此时mutex等于0,其他进程就无法访问了,进程结束后,要signal(mutex),让mutex+1,才能执行其他进程。
mutex和empty、full的位置要怎么考虑呢?
因为两个进程等待是有前提的,比如生产进程只有在缓冲区有空位时才会和消费者进行互斥,消费者也是这样,所以放在同步的里面。
void producer()
{
while(1)
{
wait(empty);
wait(mutex);
//生产
signal(mutex);
signal(full);
}
}
void consumer()
{
while(1)
{
wait(full);
wait(mutex);
//消费
signal(mutex);
signal(empty);
}
}