📜  生产者-消费者问题

📅  最后修改于: 2020-12-15 09:31:12             🧑  作者: Mango

生产者-消费者问题

生产者-消费者问题是经典的多进程同步问题,也就是说,我们正在尝试实现多个进程之间的同步。

生产者-消费者问题中有一个生产者,生产者正在生产某些物品,而有一个消费者正在消费生产者生产的物品。生产者和使用者共享相同的内存缓冲区,该缓冲区大小固定。

生产者的任务是生产项目,将其放入内存缓冲区,然后再次开始生产项目。消费者的任务是消耗内存缓冲区中的商品。

让我们了解是什么问题?

以下是生产者-消费者中出现的问题的几点考虑:

  • 生产者应仅在缓冲区未满时才产生数据。如果发现缓冲区已满,则不允许生产者将任何数据存储到内存缓冲区中。
  • 只有且仅当内存缓冲区不为空时,使用者才能使用数据。如果发现缓冲区为空,则不允许使用者使用存储缓冲区中的任何数据。
  • 生产者和使用者不应同时访问内存缓冲区。

让我们看一下上面问题的代码:

生产者代码

生产者代码

让我们了解上面的生产者和消费者代码:

在开始解释代码之前,首先,请了解以上代码中使用的几个术语:

  • 生产者代码中使用的“ in ”表示下一个空缓冲区
  • 消费者代码中使用的“ out ”表示第一个填充的缓冲区
  • count将元素的数量保留在缓冲区中
  • 计数进一步分为生产者代码和消费者代码中的块中表示的3行代码。

如果我们先谈生产者代码:

–Rp是保留m [count]值的寄存器

–Rp递增(因为元素已添加到缓冲区)

–Rp的增量值存储回m [count]

同样,如果我们接下来讨论使用者代码:

–Rc是一个寄存器,用于保留m [count]的值

–Rc递减(因为元素已从缓冲区中移出)

-Rc的递减值存储回m [count]。

缓冲


从图中可以看到:缓冲区总共有8个空间,其中前5个空间被填充,in = 5(指向下一个空位置),out = 0(指向第一个填充位置)。

让我们从想要产生元素“ F”的生产者开始,根据代码它将输入到producer()函数,而(1)始终为true,在尝试将itemP = F插入缓冲区之前,那while(count == n);将评估为False。

注意:while循环后的分号如果结果为True(即无限循环/缓冲区已满),则不会让代码继续执行

Buffer [in] = itemP→Buffer [5] =F。(现在插入F)

in =(in + 1)mod n→(5 +1)mod 8→6,因此in = 6; (下一个空缓冲区)

插入F后,Buffer看起来像这样

其中out = 0 ,但in = 6

由于count = count + 1;分为三个部分:

加载Rp,m [count]→将将计数值5复制到寄存器Rp。

增量Rp→将Rp增量为6。

假设紧接在Increment之后且在执行第三行(存储m [count],Rp)之前,发生上下文切换,并且代码跳至使用者代码。 。 。

消费者代码:

从现在开始,消费者谁想要消费的第一要素“A”,根据代码会进入消费()函数,而(1)将永远是真实的,而(计数== 0);将计算为False(由于计数仍为5,不等于0。

注意:while循环后的分号如果结果为True,则不会继续执行代码(即无限循环/缓冲区中没有元素)

itemC =缓冲区[输出]→itemC = A(因为out为0)

out =(out + 1)mod n→(0 + 1)mod 8→1,因此out = 1(第一个填充位置)

A已被删除

除去A后,Buffer看起来像这样

其中out = 1in = 6

由于count = count-1;分为三个部分:

加载Rc,m [count]→将将计数值5复制到寄存器Rp。

递减Rc→将Rc递减为4。

存储m [count],Rc→count = 4。

现在count的当前值为4

假设此上下文切换发生在生产者代码的剩余部分。 。 。

由于生产者代码的上下文切换发生在增量之后和第三行执行之前(存储m [count],Rp)

所以我们从这里恢复,因为Rp保持6为增量值

因此存储m [count],Rp→count = 6

现在count的当前值为6,这是错误的,因为Buffer只有5个元素,此条件称为“竞争条件”,而“问题是生产者-消费者问题”。

使用信号量解决生产者-消费者问题

可以通过信号量来解决由于上下文切换而产生的上述生产者和消费者问题,并且产生不一致的结果。

为了解决上述竞争条件下发生的问题,我们将使用二进制信号量和计数信号量

二进制信号量:在二进制信号量中,只有两个进程可以在任何时间竞争进入其关键部分,除此之外,还保留了互斥的条件。

计数信号量:在计数信号量时,两个以上的进程可以在任何时间竞争进入其关键部分,除此之外,还保留了互斥的条件。

信号量:信号量是S中的一个整数变量,除初始化外,信号量仅可通过两个标准原子操作-wait和signal访问,其定义如下:

1. wait( S )
{
while( S <= 0) ;
S--;
}
2. signal( S )
{
S++;
}

从以上wait的定义中可以明显看出,如果S <= 0,则它将进入无限循环(由于分号;在while循环之后)。信号的工作是增加S的值。

让我们将代码视为使用信号量(二进制和计数信号量)的生产者和消费者问题的解决方案:

生产者代码解决方案

void producer( void )
{
  wait ( empty );
  wait(S);
  Produce_item(item P)
  buffer[ in ] = item P;
  in = (in + 1)mod n
  signal(S);
  signal(full);
  
}

消费者法规解决方案

void consumer(void)
{
  wait ( empty );
  wait(S);
  itemC = buffer[ out ];
  out = ( out + 1 ) mod n;
  signal(S);
  signal(empty);
}

让我们了解上面的生产者和消费者代码解决方案:

在开始解释代码之前,首先,请了解以上代码中使用的几个术语:

  • 生产者代码中使用的“ in ”表示下一个空缓冲区
  • 消费者代码中使用的“ out ”表示第一个填充的缓冲区
  • empty ”正在计数信号量,其得分为否。空缓冲区
  • full ”是指没有得分的信号量。完整缓冲区
  • S ”是二进制信号缓冲区

如果我们看到Buffer的现状

S = 1(init。Binary semaphore的值

in = 5(下一个空缓冲区)

out = 0(第一个填充的缓冲区)

从图中可以看到:缓冲区总共有8个空间,其中前5个空间被填充,in = 5(指向下一个空位置),out = 0(指向第一个填充位置)。

生产者代码中使用的信号量:

6.等待(空)将降低计数的值信号变量通过1,即当制片产生一些元件则空间的值在缓冲就会自动减少一个。如果缓冲区已满,则计数信号量变量“ empty”的值为0,然后wait(empty);将捕获该进程(按照等待的定义),并且不允许进行进一步操作。

7. wait(S)将二进制信号量变量S减小为0,以便不允许任何其他愿意进入其关键部分的进程。

8.信号将二进制信号量变量S增大为1,以便现在可以允许其他愿意进入其关键部分的进程。

9. signal(full)将计数信号量变量full增加1,因为将项目添加到缓冲区时,缓冲区中将占用一个空间,并且必须更新变量full。

生产者代码中使用的信号量:

10.0wait(full)将使计数信号量变量full的值减少1,即当使用者使用某些元素时,则缓冲区中的全部空间的值将自动减少1。如果缓冲区为空,则计数信号量变量full的值为0,则wait(full);将捕获该进程(按照wait的定义),并且不允许进行进一步操作。

11. wait(S)将二进制信号量变量S减小为0,因此不允许其他任何愿意进入其关键部分的进程。

12. signal(S)将二进制信号量变量S增大为1,以便现在可以允许其他愿意进入其关键部分的进程。

13. signal(empty)将计数信号量变量empty增加1,因为从缓冲区中删除一项时,缓冲区中有一个空位,并且empty变量必须相应地更新。

生产者代码:

让我们从想要产生元素“ F”的producer()开始,根据代码它将输入到producer()函数。

等待(空);将空值减1,即空= 2

假设在此上下文切换发生之后并跳转到使用者代码。

消费者代码:

现在开始要消费第一个元素“ A”的消费者,根据代码它将进入consumer()函数,

等待(满);将使full的值减少1,即full = 4

等待(S);将S的值减小到0

itemC = Buffer [out]; →itemC = A(因为out为0)

A已被删除

out =(out + 1)mod n→(0 + 1)mod 8→1,因此out = 1(第一个填充位置)

S = 0(二进制信号量的值)

in = 5(下一个空缓冲区)

out = 1(第一个填充的缓冲区)

假设在此上下文之后,切换回生产者代码

由于生产者()的下一条指令是wait(S);,这将捕获生产者进程,因为S的当前值为0,并且等待(0);是一个无限循环:按照等待的定义,因此生产者无法继续前进。

因此,我们回到下一个消费者流程指令。

信号(S);现在将S的值增加到1。

信号(空);将空值增加1,即空值= 3

现在移回producer()代码;

由于producer()的下一条指令是wait(S);将成功执行,因为S现在为1,它将S的值减1,即S = 0

缓冲区[in] = itemP; →缓冲区[5] =F。(现在插入F)

in =(in + 1)mod n→(5 +1)mod 8→6,因此in = 6; (下一个空缓冲区)

信号(S);将S加1,

信号(满);将以全数递增1,即全数= 5

现在添加当前值full和empty,即full + empty = 5 + 3 = 8(这是绝对好的),即使在进行了如此多的上下文切换之后也不会产生不一致的结果。但是在没有信号量的生产者和消费者的先前条件下,我们看到在上下文切换的情况下结果不一致。

这是生产者消费者问题的解决方案。