生产者-消费者问题
1. 问题描述
一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区为满时,生产者才把消息放入缓冲区,否则必须等待;只有缓冲区不为空时,消费者从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。
2. 问题分析
- 生产者与消费者之间对于缓冲区的访问是一个互斥关系。
- 如果生产者对缓冲区的写入操作不互斥,则可能造成进程p1和进程p2同时获得缓冲区的某个位置为空,进程p1要往缓冲区的该位置写入数据,同时p2也要往该位置写入数据,导致两个进程往其中写入数据的时候出现了数据覆盖的现象。(可能是进程p1先写入数据之后进程p2又立即往相应的位置写入了数据)
- 如果消费者读取数据的时候没有互斥,则可能造成两个消费者进程同时读取某一段数据,导致各自只读取了数据的一部分,造成读取错误。
- 生产者与消费者又是互相协作的一个关系,必须有生产者的生产之后才有消费者的消费,因此生产者与消费者之间又是一个同步的关系。
- 实现互斥部分:实现互斥部分要通过PV操作对互斥的代码操作进行互斥加紧:这里也就是生产者对缓冲区的写以及消费者对缓冲区的读。(互斥加紧也就是在互斥代码段前紧跟一个P操作,在互斥代码后紧跟一个V操作)
- 对于设置的信号量:
- 如果mutex的值为0,则P(mutex)操作会导致进程阻塞状态
- 如果empty的值为0,则P(empty)操作会导致进程阻塞状态
- 如果full的值为0,则P(full)操作会导致进程阻塞状态
- 实现同步部分:
- 在缓冲区为空时,必须有生产者的写才能有消费者的读操作,如果没有生产者的写,则消费者会由于full值为0而引发p(full)造成的消费者进程阻塞,因而需要一个v(full)操作(来决定是否唤醒消费者进程)在前,一个p(full)操作在后决定是否阻塞消费者进程。因而在producer进程的末尾有一个v(full),在consumer的开头有一个p(full)
- 在缓冲区为满时,必须有消费者的读操作,才能有生产者的写操作,因而在producer进程前必须有一个p(empty)操作在empty=0时进行对生产者进程的阻塞,相对应,在consumer进程末尾必须有一个v(empty)进程来决定在empty不再为0的时候唤醒被阻塞的producer进程。
- 关于while(1)的作用:保证只要进程没有在被阻塞的状态就能持续不断的接受处理器,保证进程运行的连续性。
3. 进程描述代码
semaphore mutex = 1;//设置临界区互斥信号量
semaphore empty = n;//设置空闲缓冲区
semaphore full = 0;//缓冲区初始化为空
poducer(){
while(1){
生产一个产品;
p(empty);
p(mutex);
将产品加入缓冲区;//互斥部分
v(mutex);
v(full);
}
}
consumer(){
while(1){
p(full);
p(mutex);
从缓冲区取出数据;//互斥部分
v(mutex);
v(empty);
}
}
4. 补充
不能调换p(empty)和p(mutex)的顺序,也不能调换p(full)和p(mutex)的顺序。否则可能在缓冲区为空或者缓冲区为满的时候造成死锁
分析:假如empty=0,mutex=1,则在进行producer进程的时候,首先进行p(mutex),发现mutex为1,缓冲区没有被占用,则给缓冲区上锁,将mutex设置成0,然后再进行p(empty)操作,发现empty为0,将producer进程阻塞,于是缓冲区就处于一个上锁的状态,consumer进程也不能再访问,这就造成了死锁。