操作系统 睡眠和唤醒
生产者消费者问题
让我们来观察一下基本模型,即睡眠和唤醒。假设我们有两个系统调用,分别是 睡眠 和 唤醒 。调用睡眠的进程将被阻塞,而调用唤醒的进程将被唤醒。
有一个流行的例子叫做 生产者消费者问题 ,它是模拟 睡眠和唤醒 机制的最流行的问题。
睡眠和唤醒的概念非常简单。如果临界区不为空,则进程将进入睡眠状态。它将被当前在临界区内执行的其他进程唤醒,这样进程就可以进入临界区。
在生产者消费者问题中,假设有两个进程,一个进程写入数据,另一个进程读取数据。写入数据的进程称为 生产者 ,而读取数据的进程称为 消费者 。
为了读取和写入,它们都使用一个缓冲区。下面是用于模拟睡眠和唤醒机制,以解决生产者消费者问题的代码。
#define N 100 //maximum slots in buffer
#define count=0 //items in the buffer
void producer (void)
{
int item;
while(True)
{
item = produce_item(); //producer produces an item
if(count == N) //if the buffer is full then the producer will sleep
Sleep();
insert_item (item); //the item is inserted into buffer
count=count+1;
if(count==1) //The producer will wake up the
//consumer if there is at least 1 item in the buffer
wake-up(consumer);
}
}
void consumer (void)
{
int item;
while(True)
{
{
if(count == 0) //The consumer will sleep if the buffer is empty.
sleep();
item = remove_item();
count = count - 1;
if(count == N-1) //if there is at least one slot available in the buffer
//then the consumer will wake up producer
wake-up(producer);
consume_item(item); //the item is read by consumer.
}
}
}
生产者生产物品并将其插入缓冲区。全局变量count的值在每次插入时增加。如果缓冲区被完全填满并且没有可用的插槽,那么生产者将休眠,否则它会继续插入。
在消费者端,每次消费count的值减少1。如果在任何时候缓冲区为空,则消费者将休眠,否则它会继续消费物品并将count的值减少1。
如果缓冲区中至少有1个可供消费的物品,生产者将唤醒消费者。如果缓冲区中至少有一个插槽可用,消费者将唤醒生产者以便生产者可以写入。
好吧,在消费者打算休眠之前被抢占的情况下出现问题。现在消费者既没有休眠也没有消费。由于生产者不知道消费者实际上没有休眠,因此它在消费者不响应的情况下继续唤醒消费者。
这会导致系统调用的浪费。当消费者再次被调度时,它将休眠,因为它在被抢占时正准备休眠。
生产者继续写入缓冲区,一段时间后缓冲区被填满。此时,生产者也会休眠,想着当缓冲区中有一个插槽可用时消费者会唤醒它。
消费者也在休眠,不知道生产者会唤醒它。
这是一种死锁情况,生产者和消费者都处于非活动状态,并等待对方将其唤醒。这是一个需要解决的严重问题。
使用标志位来解决这个问题
可以使用一个标志位来解决这个问题。生产者在第一次调用唤醒时可以设置该标志位。当消费者被调度时,它会检查该标志位。
现在消费者将知道生产者尝试唤醒它,因此它不会休眠,并处于就绪状态以消费生产者产生的物品。
这个解决方案仅针对一对生产者和消费者有效,如果有n个生产者和n个消费者怎么办。在这种情况下,需要维护一个可以记录已进行了多少次唤醒调用和有多少消费者不需要休眠的整数。这个整数变量称为信号量。我们将在稍后详细讨论信号量。