在线程通信中,消息队列相对于共享数据有什么优势?

what's the advantage of message queue over shared data in thread communication?

本文关键字:数据 什么 共享 相对于 通信 线程 队列 消息      更新时间:2023-10-16

我读过一篇关于多线程程序设计http://drdobbs.com/architecture-and-design/215900465的文章,它说这是"用异步消息替换共享数据"的最佳实践。尽可能地保持每个线程的数据隔离(非共享),并让线程通过传递数据副本的异步消息进行通信。

让我困惑的是,我没有看到使用共享数据和消息队列之间的区别。我现在正在做一个windows上的非gui项目,所以让我们使用windows的消息队列。并以传统的生产者-消费者问题为例。

使用共享数据,在生产者线程和消费者线程之间会有一个共享的容器和一个保护容器的锁。当生产者输出product时,它首先等待锁,然后向容器中写入内容,然后释放锁。

使用消息队列,生产者可以简单地PostThreadMessage而不需要阻塞。这就是异步消息的优势。但是我认为在两个线程之间一定存在一些锁来保护消息队列,否则数据肯定会损坏。PostThreadMessage调用只是隐藏细节。我不知道我的猜测是否正确,但如果是真的,优势似乎不再存在,因为这两种方法都做同样的事情,唯一的区别是系统在使用消息队列时隐藏了细节。

p。也许消息队列使用非阻塞容器,但我也可以以前一种方式使用并发容器。我想知道消息队列是如何实现的,这两种方式之间是否存在性能差异?

更新:如果消息队列操作仍然在其他地方阻塞,我仍然不理解异步消息的概念。如果我猜错了,请纠正我:当我们使用共享容器和锁时,我们将在自己的线程中阻塞。但是当使用消息队列时,我自己的线程立即返回,并将阻塞工作留给其他系统线程。

消息传递 对于交换少量数据很有用,因为不需要避免冲突。它比实现计算机间通信的共享内存要容易得多。此外,正如您已经注意到的,消息传递有一个优点,即应用程序开发人员不需要担心诸如共享内存之类的保护细节。

共享内存 允许最大速度和方便的通信,因为它可以在计算机内以内存速度完成。共享内存通常比消息传递快,因为消息传递通常使用系统调用实现,因此需要执行更耗时的内核干预任务。相反,在共享内存系统中,只需要系统调用来建立共享内存区域。一旦建立,所有的访问都被视为正常的内存访问,不需要内核的额外帮助。

Edit:您可能希望实现自己的队列的一种情况是,有许多消息要生成和使用,例如,日志系统。通过实现PostThreadMessage,它的队列容量是固定的。如果超过该容量,消息很可能会丢失。

假设您有1个线程生成数据,4个线程处理该数据(假设是为了使用多核机器)。如果你有一个大的全局数据池,当任何线程需要访问时,你可能不得不锁定它,这可能会阻塞其他3个线程。随着您添加更多的处理线程,您增加了锁必须等待的机会增加了可能需要等待的东西的数量。最终,添加更多的线程一无所获,因为你所做的只是花更多的时间阻塞。

如果你有一个线程发送消息到消息队列,每个消费者线程一个,那么他们就不能互相阻塞。你仍然需要锁定生产者和消费者线程之间的队列,但是由于每个线程都有一个单独的队列,所以你有一个单独的锁,并且每个线程不能阻塞所有其他等待数据的线程。

如果你突然得到一个32核的机器,你可以添加20多个处理线程(和队列),并期望性能将相当线性地扩展,而不像第一种情况那样,新线程总是相互碰撞。

我使用了一个共享内存模型,其中指向共享内存的指针在消息队列中进行管理,并小心锁定。从某种意义上说,这是消息队列和共享内存的混合体。当必须在线程之间传递大量数据,同时保持消息队列的安全性时,这是非常重要的。

整个队列可以打包在一个c++类中,并具有适当的锁定等功能。关键是队列拥有共享存储并负责锁定。生产者为队列的输入获取一个锁,并接收一个指向下一个可用存储块(通常是某种类型的对象)的指针,填充它并释放它。消费者将阻塞,直到下一个共享对象被生产者释放。然后,它可以获取存储的锁,处理数据并将其释放回池。在适当设计的队列中,可以高效地执行多个生产者/多个消费者操作。考虑一个Java线程安全(Java .util.concurrent. blockingqueue)语义,但是对于指向存储的指针。

传递消息时当然存在"共享数据"。毕竟,消息本身就是某种数据。然而,重要的区别是,当您传递消息时,消费者将收到副本

PostThreadMessage调用只是隐藏细节

是的,它会,但是作为一个WINAPI调用,你可以合理地确定它会正确地完成。

如果消息队列操作仍然在其他地方阻塞,我仍然不明白异步消息的概念。

优点更安全。在传递消息时,您有一个系统地强制执行的锁定机制。你甚至不需要想,你不能忘记锁门。考虑到多线程bug是一些最令人讨厌的bug(想想竞争条件),这是非常重要的。消息传递是建立在锁之上的更高层次的抽象。

的缺点是传递大量数据可能很慢。在这种情况下,您需要使用共享内存。

对于传递状态(即工作线程向GUI报告进度),消息是要走的路。

这很简单(我很惊讶其他人写出这么长的回复!):

使用消息队列系统而不是"原始"共享数据意味着你只需要在中心位置正确地进行一次同步(锁定/解锁资源)。

对于基于消息的系统,您可以从更高的角度考虑"消息",而不必再担心同步问题。就其价值而言,消息队列完全有可能在内部使用共享数据实现。

我认为这是那里的关键信息:"尽可能多地,更喜欢保持每个线程的数据隔离(非共享),而让线程通过传递数据副本的异步消息进行通信"。即使用生产者-消费者:)
您可以执行自己的消息传递或使用操作系统提供的东西。这是一个实现细节(需要立即完成)。关键是要避免共享数据,例如让多个线程修改同一内存区域。这可能会导致难以发现的bug,而且即使代码是完美的,也会因为所有的锁定而降低性能。

我也有同样的问题。读完答案后。我觉得:

  1. 在大多数典型用例中,队列= async,共享内存(锁)= sync。实际上,您可以使用异步版本的共享内存,但这需要更多的代码,类似于重新发明消息传递轮。

  2. 更少的代码=更少的bug和更多的时间专注于其他事情。

前面的答案已经提到了利弊,所以我就不再重复了。