生产者消费者和沉睡理发师的区别

Difference between Producer Consumer and Sleeping Barber

我正在学习同步问题并阅读有关 Producer Consumer problem and Sleeping Barber problem 的内容。

我发现Producer Consumer问题和Sleeping Barber问题很相似。坦率地说,我找不到它们之间的区别。

让我说... 当生产者做出产品时,他将其添加到队列中;当一位顾客到达理发店时,他会去等候室。 当然,如果顾客在睡觉,他会去找理发师叫醒他。它在产品消费者问题中看起来很相似。如果队列为空,消费者可能会休眠,当有新产品加入队列时应该有人叫醒他。

当排队满了的时候,生产者不应该再做更多的产品,这样他就可以睡得更好了。当消费者从队列中消费了一个产品,并且队列中有位置时,生产者就应该醒来(或者应该被某人唤醒)去工作。 我认为这与 Sleeping Barber 问题类似。当等候室客满时,新来的顾客可以在等候室的位置等候。当候诊室有人去理发时,外面的顾客可以进入候诊室。 (当然,如果等候室没有空椅子,顾客可以直接回家,但我觉得差别不大)

我认为解决这两个问题的实现方式是相似的。在各种版本的实现中,我看到了使用两个信号量和一个互斥锁的实现。两个信号量用于唤醒睡眠中的参与者,一个互斥量用于防止并发访问损坏数据区域。 我相信这个解决方案可以解决这两个问题。所以我觉得Producer Consumer问题和Sleeping Barber问题没有区别。

Producer-Consumer问题是关于producer和consumer有不同throughputs,所以如果Producer生产任务快于Consumer执行它们,那么他们之间的任务队列的大小将会增长,因此从任务到达队列的那一刻到消费者从队列中取出它的那一刻起,任务的时间都会增加,最终你会内存不足。

睡觉的理发师问题大约是 race conditions。想象一下,您有相同的生产者生成任务(来理发店的人)和消费者(理发师)。如果不执行 busy waiting 当队列中没有更多任务时,您的 Consumer 会休眠,因此当新任务到达时,它首先通知 Consumer 它不应该休眠。那么现在想象一个情况,当你的Consumer当前正在执行任务A,而任务B到达时,它看到Consumer正在工作并且会去队列,但它不是atomic 操作,所以在这个检查(消费者很忙)和将自己添加到队列之间,消费者可以已经完成任务 A 并检查队列,什么也看不到(因为 B 仍然没有添加),然后去睡觉,但是 B 不知道这一点,并且会等到最终另一个任务 C 来唤醒消费者。

我希望这表明这些问题是不同的,并且它们有许多不同的解决方法(您可以轻松 google 一些方法来解决它们)。

例如在java中你可以使用BlockingQueue来解决睡觉的理发师问题,所以基本上队列本身会唤醒你的Producer,以防有新的任务要执行。

解决Producer-Consumer问题的一种方法,也是使用固定大小的BlockingQueue,所以当queue满时,Producer会blocked,当它会尝试添加更多任务,直到队列中有更多空闲 space。

这些问题各不相同,有些解决方案只解决其中一个问题,而不是同时解决两个问题。比如consumer-producer问题,除了睡觉,看你的需求,还有其他的策略:

  • 当队列已满时,只需丢弃生产者创建的新任务。这看起来愚蠢且效率低下,但在现实世界中,当您不需要执行所有生成的任务时,有时会使用这种方法。一个相当简单的例子可能是你有一个创建一些任务的生产者,但是这些任务有超时并且超时是由一些第三方数据提供者设置的,所以时间甚至在你的消费者实际创建任务之前就开始了,所以休眠不是一种选择,如果队列已满,那么你不太可能及时完成这项任务,如果你增加队列,不仅这个任务会失败,而且下一个任务也会因超时而失败,最终会发生什么 - 那里将没有及时执行的任务。所以你的选择是放弃一些新的任务,这样在某个时候,你可以再次添加它们,它们就会及时执行。这是一个真实世界的例子,来自银行发送的交易利率报价。
  • 解决这个消费者-生产者问题的另一个选择是使用来自响应式编程的 reactive streams。简单地说,就是当您的消费者实际告诉您的生产者有关其吞吐量的信息时。可以是他此刻准备消费的任务数,也可以是每秒的任务数。例如,你可以看看这个 implementation or this article

这两种方法解决了消费者生产者问题,但它们没有解决睡觉的理发师问题,因为它们没有具体说明生产者与任务队列的通信。

我认为您弄错了睡觉理发师的问题,因为候诊室(队列)不是满的,而是空的。问题是,如果等候室是空的,而理发师正忙于其他人,如果没有某种同步,你会发现自己处于错误的状态。(在消费者-生产者问题中,你总是处于正确的状态)。特别是新顾客来了,看到理发师很忙,就去等候室,想象一下,这个等候室很远,到那里需要一些时间,而他走到这个房间的时候,理发师已经完成了工作,并用摄像机检查是否有人在这个房间里等候,但此刻没有人在那里,因为你还在继续,所以理发师睡着了,你到达等候室并永远坐在那里,如果没有其他顾客会来叫醒理发师的。

还有一些其他的方法可以解决这个问题。然而,最常见的确实是使用 BlockingQueue。然而,在您看来 BlockingQueue 总能解决这两个问题,但它只适用于固定大小 BlockingQueue。您也可以拥有例如没有任何容量限制的阻塞队列(显然除了您的堆大小),例如LinkedBlockingQueue,那么生产者将永远不会休眠,因为队列只会在需要时增加其容量。它也是广泛使用的方法。因为有时候你无法停止你的生产者,因为它可能是一些远程第三方系统,它永远不会停止,只会为你生产新任务,你需要消耗所有这些,你显然不能让它停止,因为可能有其他客户在收听此数据,并且当只有一个客户的速度不如其他客户时,他们不想停止。所以你需要使用没有固定大小的队列,所以我们将通过阻塞队列解决睡眠理发师问题,但我们仍然会遇到吞吐量问题,这意味着消费者 - 生产者问题。为了解决这个问题,我们也可以创建一些观察者线程,它有时会检查队列的当前大小,如果在某个时候它发现队列大小显着增加,它可以为此队列创建另一个消费者,或者通知开发人员有是有问题,还是做一些其他的事情。然而这个观察者根本没有帮助我们解决睡觉的理发师问题。

我需要指出的是,这些并不是解决这些问题的唯一方法,它们各有优缺点。

我试图突出显示您可以 google 完全理解的单词。